Merge pull request #76 from EETagent/multiple_parents

(backend) Multiple parents
This commit is contained in:
Vojtěch Jungmann 2022-12-06 00:20:07 +01:00 committed by GitHub
commit 2fd479a0e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 516 additions and 248 deletions

View file

@ -2,7 +2,7 @@ use std::net::{SocketAddr, IpAddr, Ipv4Addr};
use portfolio_core::{ use portfolio_core::{
crypto::random_8_char_string, 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 requests::{AdminLoginRequest, RegisterRequest};
use rocket::http::{Cookie, Status, CookieJar}; use rocket::http::{Cookie, Status, CookieJar};
@ -146,10 +146,15 @@ pub async fn get_candidate(
let db = conn.into_inner(); let db = conn.into_inner();
let private_key = session.get_private_key(); 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( let details = ApplicationService::decrypt_all_details(
private_key, private_key,
db, db,
id candidate
) )
.await .await
.map_err(to_custom_error)?; .map_err(to_custom_error)?;

View file

@ -86,7 +86,7 @@ pub async fn post_details(
let form = details.into_inner(); let form = details.into_inner();
let candidate: entity::candidate::Model = session.into(); 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 .await
.map_err(to_custom_error)?; .map_err(to_custom_error)?;
@ -103,7 +103,7 @@ pub async fn get_details(
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
// let handle = tokio::spawn(async move { // 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 .await
.map(|x| Json(x)) .map(|x| Json(x))
.map_err(to_custom_error); .map_err(to_custom_error);
@ -284,21 +284,27 @@ mod tests {
} }
const CANDIDATE_DETAILS: &'static str = "{ const CANDIDATE_DETAILS: &'static str = "{
\"name\": \"idk\", \"candidate\": {
\"surname\": \"idk\", \"name\": \"idk\",
\"birthplace\": \"Praha 1\", \"surname\": \"idk\",
\"birthdate\": \"2015-09-18\", \"birthplace\": \"Praha 1\",
\"address\": \"Stefanikova jidelna\", \"birthdate\": \"2015-09-18\",
\"telephone\": \"000111222333\", \"address\": \"Stefanikova jidelna\",
\"citizenship\": \"Czech Republic\", \"telephone\": \"000111222333\",
\"email\": \"magor@magor.cz\", \"citizenship\": \"Czech Republic\",
\"sex\": \"MALE\", \"email\": \"magor@magor.cz\",
\"personalIdNumber\": \"0000000000\", \"sex\": \"MALE\",
\"study\": \"KB\", \"personalIdNumber\": \"0000000000\",
\"parentName\": \"maminka\", \"study\": \"KB\"
\"parentSurname\": \"chad\", },
\"parentTelephone\": \"420111222333\", \"parents\": [
\"parentEmail\": \"maminka@centrum.cz\" {
\"name\": \"maminka\",
\"surname\": \"chad\",
\"telephone\": \"420111222333\",
\"email\": \"maminka@centrum.cz\"
}
]
}"; }";
#[test] #[test]

View file

@ -1,4 +1,4 @@
use crate::{Mutation, models::candidate_details::EncryptedApplicationDetails}; use crate::{Mutation, models::candidate_details::{EncryptedCandidateDetails}};
use ::entity::candidate::{self}; use ::entity::candidate::{self};
use sea_orm::*; use sea_orm::*;
@ -44,20 +44,20 @@ impl Mutation {
pub async fn add_candidate_details( pub async fn add_candidate_details(
db: &DbConn, db: &DbConn,
user: candidate::Model, user: candidate::Model,
enc_details: EncryptedApplicationDetails, enc_candidate: EncryptedCandidateDetails,
) -> Result<candidate::Model, sea_orm::DbErr> { ) -> Result<candidate::Model, sea_orm::DbErr> {
let mut user: candidate::ActiveModel = user.into(); let mut user: candidate::ActiveModel = user.into();
user.name = Set(Some(enc_details.name.into())); user.name = Set(Some(enc_candidate.name.into()));
user.surname = Set(Some(enc_details.surname.into())); user.surname = Set(Some(enc_candidate.surname.into()));
user.birthplace = Set(Some(enc_details.birthplace.into())); user.birthplace = Set(Some(enc_candidate.birthplace.into()));
user.birthdate = Set(Some(enc_details.birthdate.into())); user.birthdate = Set(Some(enc_candidate.birthdate.into()));
user.address = Set(Some(enc_details.address.into())); user.address = Set(Some(enc_candidate.address.into()));
user.telephone = Set(Some(enc_details.telephone.into())); user.telephone = Set(Some(enc_candidate.telephone.into()));
user.citizenship = Set(Some(enc_details.citizenship.into())); user.citizenship = Set(Some(enc_candidate.citizenship.into()));
user.email = Set(Some(enc_details.email.into())); user.email = Set(Some(enc_candidate.email.into()));
user.sex = Set(Some(enc_details.sex.into())); user.sex = Set(Some(enc_candidate.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.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_details.study.into())); user.study = Set(Some(enc_candidate.study.into()));
user.updated_at = Set(chrono::offset::Local::now().naive_local()); user.updated_at = Set(chrono::offset::Local::now().naive_local());
@ -117,7 +117,7 @@ mod tests {
vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()], vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
).await.unwrap(); ).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) let candidate = Query::find_candidate_by_id(&db, APPLICATION_ID)
.await .await

View file

@ -1,4 +1,4 @@
use crate::{Mutation, models::candidate_details::EncryptedApplicationDetails}; use crate::{Mutation, models::candidate_details::{EncryptedParentDetails}};
use ::entity::parent::{self, Model}; use ::entity::parent::{self, Model};
use sea_orm::*; use sea_orm::*;
@ -18,17 +18,17 @@ impl Mutation {
pub async fn add_parent_details( pub async fn add_parent_details(
db: &DbConn, db: &DbConn,
parent: Model, parent: Model,
enc_details: EncryptedApplicationDetails, // TODO: use seperate struct?? enc_parent: EncryptedParentDetails,
) -> Result<Model, sea_orm::DbErr> { ) -> Result<Model, sea_orm::DbErr> {
let mut user: parent::ActiveModel = parent.into(); let mut parent: parent::ActiveModel = parent.into();
user.name = Set(Some(enc_details.parent_name.into())); parent.name = Set(Some(enc_parent.name.into()));
user.surname = Set(Some(enc_details.parent_surname.into())); parent.surname = Set(Some(enc_parent.surname.into()));
user.telephone = Set(Some(enc_details.parent_telephone.into())); parent.telephone = Set(Some(enc_parent.telephone.into()));
user.email = Set(Some(enc_details.parent_email.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 .await
.unwrap(); .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()); assert!(parent.is_some());
} }
@ -88,11 +88,11 @@ mod tests {
.await .await
.unwrap(); .unwrap();
Mutation::add_parent_details(&db, parent, encrypted_details) Mutation::add_parent_details(&db, parent.clone(), encrypted_details.parents[0].clone())
.await .await
.unwrap(); .unwrap();
let parent = Query::find_parent_by_id(&db, APPLICATION_ID) let parent = Query::find_parent_by_id(&db, parent.id)
.await .await
.unwrap() .unwrap()
.unwrap(); .unwrap();

View file

@ -1,18 +1,33 @@
use entity::candidate;
use entity::parent;
use entity::parent::Model; use entity::parent::Model;
use entity::parent::Entity; use entity::parent::Entity;
use sea_orm::ModelTrait;
use sea_orm::{DbConn, DbErr}; use sea_orm::{DbConn, DbErr};
use sea_orm::EntityTrait; use sea_orm::EntityTrait;
use crate::Query; use crate::Query;
impl Query { impl Query {
#[deprecated(note = "Use find_candidate_parents instead")]
pub async fn find_parent_by_id( pub async fn find_parent_by_id(
db: &DbConn, db: &DbConn,
application_id: i32, id: i32,
) -> Result<Option<Model>, DbErr> { ) -> Result<Option<Model>, 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<Vec<Model>, DbErr> {
candidate.find_related(parent::Entity)
.all(db)
.await
} }
} }

View file

@ -23,6 +23,8 @@ pub enum ServiceError {
#[error("Parrent not found")] #[error("Parrent not found")]
ParentNotFound, ParentNotFound,
#[error("Database error")] #[error("Database error")]
ParentOverflow,
#[error("Too many parents")]
DbError(#[from] sea_orm::DbErr), DbError(#[from] sea_orm::DbErr),
#[error("User not found, please contact technical support")] #[error("User not found, please contact technical support")]
UserNotFoundByJwtId, UserNotFoundByJwtId,
@ -78,6 +80,7 @@ impl ServiceError {
ServiceError::UserAlreadyExists => 409, ServiceError::UserAlreadyExists => 409,
ServiceError::CandidateNotFound => 404, ServiceError::CandidateNotFound => 404,
ServiceError::ParentNotFound => 500, ServiceError::ParentNotFound => 500,
ServiceError::ParentOverflow => 400, // TODO: correct error code
ServiceError::DbError(_) => 500, ServiceError::DbError(_) => 500,
ServiceError::UserNotFoundByJwtId => 500, ServiceError::UserNotFoundByJwtId => 500,
ServiceError::UserNotFoundBySessionId => 500, ServiceError::UserNotFoundBySessionId => 500,

View file

@ -27,11 +27,10 @@ pub struct BaseCandidateResponse {
pub submitted: bool, pub submitted: bool,
} }
/// Candidate details (admin and candidate endpoints)
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ApplicationDetails { pub struct CandidateDetails {
// Candidate // pub application_id: i32,
pub name: String, pub name: String,
pub surname: String, pub surname: String,
pub birthplace: String, pub birthplace: String,
@ -43,11 +42,24 @@ pub struct ApplicationDetails {
pub sex: String, pub sex: String,
pub study: String, pub study: String,
pub personal_id_number: String, pub personal_id_number: String,
// Parent }
pub parent_name: String, #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub parent_surname: String, #[serde(rename_all = "camelCase")]
pub parent_telephone: String, pub struct ParentDetails {
pub parent_email: String, // 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<ParentDetails>,
} }
/// CSV export (admin endpoint) /// CSV export (admin endpoint)

View file

@ -4,14 +4,15 @@ use entity::{candidate, parent};
use crate::{crypto, models::candidate::{CandidateWithParent, ApplicationDetails}, error::ServiceError}; use crate::{crypto, models::candidate::{CandidateWithParent, ApplicationDetails}, error::ServiceError};
use super::candidate::{CandidateDetails, ParentDetails};
pub const NAIVE_DATE_FMT: &str = "%Y-%m-%d"; pub const NAIVE_DATE_FMT: &str = "%Y-%m-%d";
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct EncryptedString(String); pub struct EncryptedString(String);
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct EncryptedApplicationDetails { pub struct EncryptedCandidateDetails {
// Candidate
pub name: EncryptedString, pub name: EncryptedString,
pub surname: EncryptedString, pub surname: EncryptedString,
pub birthplace: EncryptedString, pub birthplace: EncryptedString,
@ -23,12 +24,19 @@ pub struct EncryptedApplicationDetails {
pub sex: EncryptedString, pub sex: EncryptedString,
pub personal_id_number: EncryptedString, pub personal_id_number: EncryptedString,
pub study: String, pub study: String,
}
// Parent #[derive(Debug, Clone)]
pub parent_name: EncryptedString, pub struct EncryptedParentDetails {
pub parent_surname: EncryptedString, pub name: EncryptedString,
pub parent_telephone: EncryptedString, pub surname: EncryptedString,
pub parent_email: EncryptedString, pub telephone: EncryptedString,
pub email: EncryptedString,
}
#[derive(Debug, Clone)]
pub struct EncryptedApplicationDetails {
pub candidate: EncryptedCandidateDetails,
pub parents: Vec<EncryptedParentDetails>,
} }
impl EncryptedString { impl EncryptedString {
@ -86,11 +94,11 @@ impl TryFrom<Option<NaiveDate>> for EncryptedString {
} }
} }
impl EncryptedApplicationDetails { impl EncryptedCandidateDetails {
pub async fn new( pub async fn new(
form: &ApplicationDetails, form: &CandidateDetails,
recipients: Vec<String>, recipients: Vec<String>,
) -> Result<EncryptedApplicationDetails, ServiceError> { ) -> Result<EncryptedCandidateDetails, ServiceError> {
let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string();
let d = tokio::try_join!( let d = tokio::try_join!(
EncryptedString::new(&form.name, &recipients), EncryptedString::new(&form.name, &recipients),
@ -103,34 +111,26 @@ impl EncryptedApplicationDetails {
EncryptedString::new(&form.email, &recipients), EncryptedString::new(&form.email, &recipients),
EncryptedString::new(&form.sex, &recipients), EncryptedString::new(&form.sex, &recipients),
EncryptedString::new(&form.personal_id_number, &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 { Ok(
name: d.0, EncryptedCandidateDetails {
surname: d.1, name: d.0,
birthplace: d.2, surname: d.1,
birthdate: d.3, birthplace: d.2,
address: d.4, birthdate: d.3,
telephone: d.5, address: d.4,
citizenship: d.6, telephone: d.5,
email: d.7, citizenship: d.6,
sex: d.8, email: d.7,
personal_id_number: d.9, sex: d.8,
study: form.study.clone(), 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,
})
} }
pub async fn decrypt(self, priv_key: String) -> Result<ApplicationDetails, ServiceError> { pub async fn decrypt(self, priv_key: String) -> Result<CandidateDetails, ServiceError> {
let d = tokio::try_join!( let d = tokio::try_join!(
self.name.decrypt(&priv_key), // 0 self.name.decrypt(&priv_key), // 0
self.surname.decrypt(&priv_key), // 1 self.surname.decrypt(&priv_key), // 1
@ -142,56 +142,153 @@ impl EncryptedApplicationDetails {
self.email.decrypt(&priv_key), // 7 self.email.decrypt(&priv_key), // 7
self.sex.decrypt(&priv_key), // 8 self.sex.decrypt(&priv_key), // 8
self.personal_id_number.decrypt(&priv_key),// 9 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 { Ok(CandidateDetails {
name: d.0, name: d.0,
surname: d.1, surname: d.1,
birthplace: d.2, birthplace: d.2,
birthdate: NaiveDate::parse_from_str(&d.3, NAIVE_DATE_FMT).unwrap(), // TODO birthdate: NaiveDate::parse_from_str(&d.3, NAIVE_DATE_FMT).unwrap(), // TODO
address: d.4, address: d.4,
telephone: d.5, telephone: d.5,
citizenship: d.6, citizenship: d.6,
email: d.7, email: d.7,
sex: d.8, sex: d.8,
personal_id_number: d.9, personal_id_number: d.9,
study: self.study, study: self.study,
}
)
}
}
impl TryFrom<candidate::Model> for EncryptedCandidateDetails {
type Error = ServiceError;
parent_name: d.10, fn try_from(
parent_surname: d.11, candidate: candidate::Model,
parent_telephone: d.12, ) -> Result<Self, Self::Error> {
parent_email: d.13, 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<String>,
) -> Result<EncryptedParentDetails, ServiceError> {
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<ParentDetails, ServiceError> {
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<parent::Model> for EncryptedParentDetails {
type Error = ServiceError;
fn try_from(
parent: parent::Model,
) -> Result<Self, Self::Error> {
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<String>,
) -> Result<EncryptedApplicationDetails, ServiceError> {
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<ApplicationDetails, ServiceError> {
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<parent::Model>)> for EncryptedApplicationDetails {
type Error = ServiceError; type Error = ServiceError;
fn try_from( fn try_from(
(candidate, parent): (candidate::Model, parent::Model), (candidate, parents): (candidate::Model, Vec<parent::Model>),
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let mut enc_parents = vec![];
for parent in parents.iter() {
enc_parents.push(
EncryptedParentDetails::try_from(parent.clone())?
);
}
Ok(EncryptedApplicationDetails { Ok(EncryptedApplicationDetails {
name: EncryptedString::try_from(candidate.name)?, candidate: EncryptedCandidateDetails::try_from(candidate)?,
surname: EncryptedString::try_from(candidate.surname)?, parents: enc_parents,
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)?,
}) })
} }
} }
@ -203,22 +300,26 @@ impl TryFrom<CandidateWithParent> for EncryptedApplicationDetails {
cp: CandidateWithParent, cp: CandidateWithParent,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
Ok(EncryptedApplicationDetails { Ok(EncryptedApplicationDetails {
name: EncryptedString::try_from(cp.name)?, candidate: EncryptedCandidateDetails {
surname: EncryptedString::try_from(cp.surname)?, name: EncryptedString::try_from(cp.name)?,
birthplace: EncryptedString::try_from(cp.birthplace)?, surname: EncryptedString::try_from(cp.surname)?,
birthdate: EncryptedString::try_from(cp.birthdate)?, birthplace: EncryptedString::try_from(cp.birthplace)?,
address: EncryptedString::try_from(cp.address)?, birthdate: EncryptedString::try_from(cp.birthdate)?,
telephone: EncryptedString::try_from(cp.telephone)?, address: EncryptedString::try_from(cp.address)?,
citizenship: EncryptedString::try_from(cp.citizenship)?, telephone: EncryptedString::try_from(cp.telephone)?,
email: EncryptedString::try_from(cp.email)?, citizenship: EncryptedString::try_from(cp.citizenship)?,
sex: EncryptedString::try_from(cp.sex)?, email: EncryptedString::try_from(cp.email)?,
personal_id_number: EncryptedString::try_from(cp.personal_identification_number)?, sex: EncryptedString::try_from(cp.sex)?,
study: cp.study.ok_or(ServiceError::CandidateDetailsNotSet)?, 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 once_cell::sync::Lazy;
use crate::crypto; use crate::{crypto, models::candidate::{CandidateDetails, ParentDetails}};
use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString}; use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString};
@ -249,40 +350,44 @@ pub mod tests {
pub static APPLICATION_DETAILS: Lazy<Mutex<ApplicationDetails>> = Lazy::new(|| pub static APPLICATION_DETAILS: Lazy<Mutex<ApplicationDetails>> = Lazy::new(||
Mutex::new(ApplicationDetails { Mutex::new(ApplicationDetails {
name: "name".to_string(), candidate: CandidateDetails {
surname: "surname".to_string(), name: "name".to_string(),
birthplace: "birthplace".to_string(), surname: "surname".to_string(),
birthdate: chrono::NaiveDate::from_ymd(2000, 1, 1), birthplace: "birthplace".to_string(),
address: "address".to_string(), birthdate: chrono::NaiveDate::from_ymd(2000, 1, 1),
telephone: "telephone".to_string(), address: "address".to_string(),
citizenship: "citizenship".to_string(), telephone: "telephone".to_string(),
email: "email".to_string(), citizenship: "citizenship".to_string(),
sex: "sex".to_string(), email: "email".to_string(),
personal_id_number: "personal_id_number".to_string(), sex: "sex".to_string(),
study: "study".to_string(), personal_id_number: "personal_id_number".to_string(),
parent_email: "parent_email".to_string(), study: "study".to_string(),
parent_name: "parent_name".to_string(), },
parent_surname: "parent_surname".to_string(), parents: vec![ParentDetails {
parent_telephone: "parent_telephone".to_string() 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) { pub fn assert_all_application_details(details: &ApplicationDetails) {
assert_eq!(details.name, "name"); assert_eq!(details.candidate.name, "name");
assert_eq!(details.surname, "surname"); assert_eq!(details.candidate.surname, "surname");
assert_eq!(details.birthplace, "birthplace"); assert_eq!(details.candidate.birthplace, "birthplace");
assert_eq!(details.birthdate, chrono::NaiveDate::from_ymd(2000, 1, 1)); assert_eq!(details.candidate.birthdate, chrono::NaiveDate::from_ymd(2000, 1, 1));
assert_eq!(details.address, "address"); assert_eq!(details.candidate.address, "address");
assert_eq!(details.telephone, "telephone"); assert_eq!(details.candidate.telephone, "telephone");
assert_eq!(details.citizenship, "citizenship"); assert_eq!(details.candidate.citizenship, "citizenship");
assert_eq!(details.email, "email"); assert_eq!(details.candidate.email, "email");
assert_eq!(details.sex, "sex"); assert_eq!(details.candidate.sex, "sex");
assert_eq!(details.study, "study"); assert_eq!(details.candidate.study, "study");
assert_eq!(details.personal_id_number, "personal_id_number"); assert_eq!(details.candidate.personal_id_number, "personal_id_number");
assert_eq!(details.parent_name, "parent_name"); assert_eq!(details.parents[0].name, "parent_name");
assert_eq!(details.parent_surname, "parent_surname"); assert_eq!(details.parents[0].surname, "parent_surname");
assert_eq!(details.parent_telephone, "parent_telephone"); assert_eq!(details.parents[0].telephone, "parent_telephone");
assert_eq!(details.parent_email, "parent_email"); assert_eq!(details.parents[0].email, "parent_email");
} }
#[tokio::test] #[tokio::test]
@ -295,19 +400,19 @@ pub mod tests {
.unwrap(); .unwrap();
assert_eq!( 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 .await
.unwrap(), .unwrap(),
"name" "name"
); );
assert_eq!( 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 .await
.unwrap(), .unwrap(),
"email" "email"
); );
assert_eq!( 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 .await
.unwrap(), .unwrap(),
"sex" "sex"

View file

@ -15,12 +15,6 @@ impl ApplicationService {
personal_id_number: String, personal_id_number: String,
) -> Result<(candidate::Model, parent::Model), ServiceError> { ) -> Result<(candidate::Model, parent::Model), ServiceError> {
Ok( 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)
)? */
( (
CandidateService::create(db, application_id, plain_text_password, personal_id_number).await?, CandidateService::create(db, application_id, plain_text_password, personal_id_number).await?,
ParentService::create(db, application_id).await? ParentService::create(db, application_id).await?
@ -30,39 +24,26 @@ impl ApplicationService {
pub async fn add_all_details( pub async fn add_all_details(
db: &DbConn, db: &DbConn,
application: i32, candidate: candidate::Model,
form: &ApplicationDetails, form: &ApplicationDetails,
) -> Result<(candidate::Model, parent::Model), ServiceError> { ) -> Result<(candidate::Model, Vec<parent::Model>), ServiceError> { // TODO: is this service needed?
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?;
Ok( Ok(
tokio::try_join!( (
CandidateService::add_candidate_details(db, candidate, enc_details.clone()), CandidateService::add_candidate_details(db, candidate.clone(), &form.candidate).await?,
ParentService::add_parent_details(db, parent, enc_details.clone()) ParentService::add_parents_details(db, candidate, &form.parents).await?
)? )
) )
} }
pub async fn decrypt_all_details( pub async fn decrypt_all_details(
private_key: String, private_key: String,
db: &DbConn, db: &DbConn,
application_id: i32, candidate: candidate::Model,
// parents: Vec<parent::Model>,
) -> Result<ApplicationDetails, ServiceError> { ) -> Result<ApplicationDetails, ServiceError> {
let candidate = Query::find_candidate_by_id(db, application_id).await? let parents = Query::find_candidate_parents(db, candidate.clone()).await?;
.ok_or(ServiceError::CandidateNotFound)?; let enc_details = EncryptedApplicationDetails::try_from((candidate, parents))?;
let parent = Query::find_parent_by_id(db, application_id).await?
.ok_or(ServiceError::ParentNotFound)?;
let enc_details = EncryptedApplicationDetails::try_from((candidate, parent))?;
enc_details.decrypt(private_key).await enc_details.decrypt(private_key).await
} }

View file

@ -2,7 +2,7 @@ use entity::candidate;
use sea_orm::{prelude::Uuid, DbConn}; use sea_orm::{prelude::Uuid, DbConn};
use crate::{ use crate::{
models::candidate_details::{EncryptedApplicationDetails, EncryptedString}, models::{candidate_details::{EncryptedApplicationDetails, EncryptedString, EncryptedCandidateDetails}, candidate::CandidateDetails},
crypto::{self, hash_password}, crypto::{self, hash_password},
error::ServiceError, error::ServiceError,
Mutation, Query, models::candidate::{BaseCandidateResponse, CreateCandidateResponse}, utils::db::get_recipients, Mutation, Query, models::candidate::{BaseCandidateResponse, CreateCandidateResponse}, utils::db::get_recipients,
@ -102,8 +102,7 @@ impl CandidateService {
) -> Result<CreateCandidateResponse, ServiceError> { ) -> Result<CreateCandidateResponse, ServiceError> {
let candidate = Query::find_candidate_by_id(db, id).await? let candidate = Query::find_candidate_by_id(db, id).await?
.ok_or(ServiceError::CandidateNotFound)?; .ok_or(ServiceError::CandidateNotFound)?;
let parent = Query::find_parent_by_id(db, id).await? let parents = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO
.ok_or(ServiceError::CandidateNotFound)?;
let new_password_plain = crypto::random_8_char_string(); let new_password_plain = crypto::random_8_char_string();
@ -125,12 +124,12 @@ impl CandidateService {
.await?; .await?;
let enc_details_opt = EncryptedApplicationDetails::try_from( let enc_details_opt = EncryptedApplicationDetails::try_from(
(candidate, parent) (candidate.clone(), parents)
); );
if let Ok(enc_details) = enc_details_opt { if let Ok(enc_details) = enc_details_opt {
let application_details = enc_details.decrypt(admin_private_key).await?; 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( Ok(
@ -150,9 +149,11 @@ impl CandidateService {
pub(in crate::services) async fn add_candidate_details( pub(in crate::services) async fn add_candidate_details(
db: &DbConn, db: &DbConn,
candidate: candidate::Model, candidate: candidate::Model,
enc_details: EncryptedApplicationDetails, details: &CandidateDetails,
) -> Result<entity::candidate::Model, ServiceError> { ) -> Result<entity::candidate::Model, ServiceError> {
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) Ok(model)
} }
@ -373,8 +374,8 @@ pub mod tests {
} }
#[cfg(test)] #[cfg(test)]
pub async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) { pub async fn put_user_data(db: &DbConn) -> (candidate::Model, Vec<parent::Model>) {
use crate::models::candidate_details::tests::APPLICATION_DETAILS; use crate::{models::candidate_details::tests::APPLICATION_DETAILS, Query};
let plain_text_password = "test".to_string(); let plain_text_password = "test".to_string();
let (candidate, _parent) = ApplicationService::create_candidate_with_parent( let (candidate, _parent) = ApplicationService::create_candidate_with_parent(
@ -383,23 +384,28 @@ pub mod tests {
&plain_text_password, &plain_text_password,
"".to_string(), "".to_string(),
) )
.await .await
.ok() .ok()
.unwrap(); .unwrap();
let form = APPLICATION_DETAILS.lock().unwrap().clone(); 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 .await
.unwrap() .unwrap();
(
candidate,
parents,
)
} }
#[tokio::test] #[tokio::test]
async fn test_put_user_data() { async fn test_put_user_data() {
let db = get_memory_sqlite_connection().await; 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!(candidate.name.is_some());
assert!(parent.name.is_some()); assert!(parents[0].name.is_some());
} }
#[tokio::test] #[tokio::test]

View file

@ -1,7 +1,7 @@
use entity::{parent}; use entity::{parent, candidate};
use sea_orm::DbConn; 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; pub struct ParentService;
@ -16,14 +16,140 @@ impl ParentService {
Ok(parent) Ok(parent)
} }
pub async fn add_parent_details( /* pub async fn add_parent_details(
db: &DbConn, db: &DbConn,
parent: parent::Model, parent: parent::Model,
enc_details: EncryptedApplicationDetails, enc_details: EncryptedParentDetails,
) -> Result<parent::Model, ServiceError> { ) -> Result<parent::Model, ServiceError> {
let parent = Mutation::add_parent_details(db, parent, enc_details) let parent = Mutation::add_parent_details(db, parent, enc_details)
.await?; .await?;
Ok(parent) Ok(parent)
} */
pub async fn add_parents_details(
db: &DbConn,
ref_candidate: candidate::Model,
parents_details: &Vec<ParentDetails>,
) -> Result<Vec<parent::Model>, 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<Mutex<ApplicationDetails>> = 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);
}
} }
} }

View file

@ -6,24 +6,26 @@ type Row = CandidateWithParent;
impl From<(i32, ApplicationDetails)> for Row { impl From<(i32, ApplicationDetails)> for Row {
fn from((application, d): (i32, ApplicationDetails)) -> Self { fn from((application, d): (i32, ApplicationDetails)) -> Self {
let c = d.candidate;
let p = d.parents[0].clone();
Self { Self {
application, application,
name: Some(d.name), name: Some(c.name),
surname: Some(d.surname), surname: Some(c.surname),
birthplace: Some(d.birthplace), birthplace: Some(c.birthplace),
birthdate: Some(d.birthdate.to_string()), birthdate: Some(c.birthdate.to_string()),
address: Some(d.address), address: Some(c.address),
telephone: Some(d.telephone), telephone: Some(c.telephone),
citizenship: Some(d.citizenship), citizenship: Some(c.citizenship),
email: Some(d.email), email: Some(c.email),
sex: Some(d.sex), sex: Some(c.sex),
study: Some(d.study), study: Some(c.study),
personal_identification_number: Some(d.personal_id_number), personal_identification_number: Some(c.personal_id_number),
parent_name: Some(d.parent_name), parent_name: Some(p.name),
parent_surname: Some(d.parent_surname), parent_surname: Some(p.surname),
parent_telephone: Some(d.parent_telephone), parent_telephone: Some(p.telephone),
parent_email: Some(d.parent_email), parent_email: Some(p.email),
} }
} }
} }

View file

@ -5,7 +5,8 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "parent")] #[sea_orm(table_name = "parent")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key)]
pub id: i32,
pub application: i32, pub application: i32,
pub name: Option<String>, pub name: Option<String>,
pub surname: Option<String>, pub surname: Option<String>,

View file

@ -11,12 +11,17 @@ impl MigrationTrait for Migration {
Table::create() Table::create()
.table(Parent::Table) .table(Parent::Table)
.if_not_exists() .if_not_exists()
.col(
ColumnDef::new(Parent::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col( .col(
ColumnDef::new(Parent::Application) ColumnDef::new(Parent::Application)
.integer() .integer()
.not_null() .not_null()
.primary_key()
.unique_key(),
) )
.col(ColumnDef::new(Parent::Name).string()) .col(ColumnDef::new(Parent::Name).string())
.col(ColumnDef::new(Parent::Surname).string()) .col(ColumnDef::new(Parent::Surname).string())
@ -26,7 +31,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Parent::UpdatedAt).date_time().not_null()) .col(ColumnDef::new(Parent::UpdatedAt).date_time().not_null())
.to_owned(), .to_owned(),
) )
.await .await
} }
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
@ -38,6 +43,7 @@ impl MigrationTrait for Migration {
#[derive(Iden)] #[derive(Iden)]
pub enum Parent { pub enum Parent {
Id,
Table, Table,
Application, Application,
Name, Name,