diff --git a/api/src/guards/data/letter.rs b/api/src/guards/data/letter.rs index 58ee4e2..a92c5ea 100644 --- a/api/src/guards/data/letter.rs +++ b/api/src/guards/data/letter.rs @@ -25,7 +25,6 @@ impl<'r> FromData<'r> for Letter { let data_bytes = data.into_bytes().await.unwrap(); if !data_bytes.is_complete() { - // TODO: Over limit return Outcome::Failure((Status::BadRequest, None)) } @@ -34,7 +33,6 @@ impl<'r> FromData<'r> for Letter { let is_pdf = portfolio_core::utils::filetype::filetype_is_pdf(&data_bytes); if !is_pdf { - // TODO: Not PDF return Outcome::Failure((Status::BadRequest, None)) } diff --git a/api/src/guards/data/portfolio.rs b/api/src/guards/data/portfolio.rs index 9029220..4953437 100644 --- a/api/src/guards/data/portfolio.rs +++ b/api/src/guards/data/portfolio.rs @@ -25,7 +25,6 @@ impl<'r> FromData<'r> for Portfolio { let data_bytes = data.into_bytes().await.unwrap(); if !data_bytes.is_complete() { - // TODO: Over limit return Outcome::Failure((Status::BadRequest, None)) } @@ -34,7 +33,6 @@ impl<'r> FromData<'r> for Portfolio { let is_zip = portfolio_core::utils::filetype::filetype_is_zip(&data_bytes); if !is_zip { - // TODO: Not ZIP return Outcome::Failure((Status::BadRequest, None)) } diff --git a/api/src/lib.rs b/api/src/lib.rs index bb0696f..bb1b2d7 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -47,7 +47,16 @@ impl Fairing for CORS { #[cfg(not(debug_assertions))] async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { - // TODO + response.set_header(Header::new( + "Access-Control-Allow-Origin", + "https://portfolio.ssps.cz", // TODO: UPRAVIT NA PRODUKČNÍ URL!! + )); + response.set_header(Header::new( + "Access-Control-Allow-Methods", + "POST, GET, OPTIONS, DELETE", + )); + response.set_header(Header::new("Access-Control-Allow-Headers", "content-type")); + response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); } } diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index b6008dc..aac339f 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -148,7 +148,7 @@ pub async fn get_candidate( let candidate = Query::find_candidate_by_id(db, id) .await - .map_err(|e| to_custom_error(ServiceError::Forbidden))? // TODO better error handling + .map_err(|e| to_custom_error(ServiceError::DbError(e)))? .ok_or(to_custom_error(ServiceError::CandidateNotFound))?; let details = ApplicationService::decrypt_all_details( diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 75a40ff..91e0cab 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -213,7 +213,6 @@ pub async fn submit_portfolio( if submit.is_err() { let e = submit.err().unwrap(); // Delete on critical error - // TODO: Více kontrol? if e.code() == 500 { // Cleanup PortfolioService::delete_portfolio(candidate.application) diff --git a/cli/src/main.rs b/cli/src/main.rs index 34ae161..fde048e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -257,7 +257,7 @@ async fn main() -> Result<(), Box> { let output = sub_matches.get_one::("output").unwrap(); tokio::fs::write(output, decrypted).await?; }, - Some(("package", sub_matches)) => { // TODO: compress the output directory into one file??? + Some(("package", sub_matches)) => { let db_url = sub_matches.get_one::("database").unwrap(); let db = get_db_conn(sub_matches).await?; let key = get_admin_private_key(&db, sub_matches).await?; diff --git a/core/src/crypto.rs b/core/src/crypto.rs index c8344f1..cc6f0a4 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -15,7 +15,6 @@ use crate::error::ServiceError; /// Foolproof random 8 char string /// only uppercase letters (except for 0 and O) and numbers -/// TODO tests pub fn random_8_char_string() -> String { let iterator = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) @@ -222,8 +221,7 @@ async fn age_encrypt_with_recipients( return Ok(()); } else { - // TODO: Error handling - unreachable!("No recipients provided"); + return Err(ServiceError::AgeNoRecipientsError); } } diff --git a/core/src/database/mutation/session.rs b/core/src/database/mutation/session.rs index f557bc0..c540398 100644 --- a/core/src/database/mutation/session.rs +++ b/core/src/database/mutation/session.rs @@ -40,5 +40,23 @@ impl Mutation { #[cfg(test)] mod tests { - // TODO: Testy + use sea_orm::prelude::Uuid; + + use crate::{utils::db::get_memory_sqlite_connection, Mutation, services::candidate_service::tests::put_user_data}; + + #[tokio::test] + async fn test_insert_delete_session() { + let db = get_memory_sqlite_connection().await; + + let session_id = Uuid::new_v4(); + let (user, _) = put_user_data(&db).await; + + let session = Mutation::insert_session(&db, Some(user.application), None, session_id, "127.0.0.1".to_string()).await.unwrap(); + + assert_eq!(session.id, session_id); + + let delete_result = Mutation::delete_session(&db, session_id).await.unwrap(); + + assert_eq!(delete_result.rows_affected, 1); + } } \ No newline at end of file diff --git a/core/src/database/query/parent.rs b/core/src/database/query/parent.rs index 01368f5..52fd6e7 100644 --- a/core/src/database/query/parent.rs +++ b/core/src/database/query/parent.rs @@ -19,7 +19,6 @@ impl Query { Entity::find_by_id(id).one(db).await } - // TODO limit to two parents?? pub async fn find_candidate_parents( db: &DbConn, candidate: candidate::Model, diff --git a/core/src/database/query/session.rs b/core/src/database/query/session.rs index 10aed95..854a2d9 100644 --- a/core/src/database/query/session.rs +++ b/core/src/database/query/session.rs @@ -32,7 +32,8 @@ impl Query { #[cfg(test)] mod tests { - use entity::{session}; + use entity::{session, admin, candidate}; + use sea_orm::ActiveValue::NotSet; use sea_orm::{prelude::Uuid, ActiveModelTrait, Set}; use crate::utils::db::get_memory_sqlite_connection; @@ -57,8 +58,7 @@ mod tests { assert!(session.is_some()); } - // TODO: Opravit test_find_sessions_by_user_id - /* #[tokio::test] + #[tokio::test] async fn test_find_sessions_by_user_id() { let db = get_memory_sqlite_connection().await; @@ -69,14 +69,14 @@ mod tests { code: Set("test".to_string()), public_key: Set("test".to_string()), private_key: Set("test".to_string()), - personal_identification_number_hash: Set("test".to_string()), + personal_identification_number: Set("test".to_string()), created_at: Set(chrono::offset::Local::now().naive_local()), updated_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); + .insert(&db) + .await + .unwrap(); session::ActiveModel { id: Set(Uuid::new_v4()), @@ -87,9 +87,9 @@ mod tests { expires_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); + .insert(&db) + .await + .unwrap(); const ADMIN_ID: i32 = 1; @@ -103,9 +103,9 @@ mod tests { updated_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); + .insert(&db) + .await + .unwrap(); session::ActiveModel { id: Set(Uuid::new_v4()), @@ -116,18 +116,14 @@ mod tests { expires_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); - - let sessions = Query::find_sessions_by_user_id(&db, Some(APPLICATION_ID), None) + .insert(&db) .await .unwrap(); + + let sessions = Query::find_sessions_by_user_id(&db, Some(APPLICATION_ID), None).await.unwrap(); assert_eq!(sessions.len(), 1); - let sessions = Query::find_sessions_by_user_id(&db, None, Some(ADMIN_ID)) - .await - .unwrap(); + let sessions = Query::find_sessions_by_user_id(&db, None, Some(ADMIN_ID)).await.unwrap(); assert_eq!(sessions.len(), 1); - } */ + } } diff --git a/core/src/error.rs b/core/src/error.rs index 5949ec9..dc5992a 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -12,10 +12,8 @@ pub enum ServiceError { Unauthorized, #[error("Forbidden")] Forbidden, - #[error("Session expired, please login agai")] + #[error("Session expired, please login again")] ExpiredSession, - #[error("Error while encoding JWT")] - JwtError, #[error("User already exists")] UserAlreadyExists, #[error("Candidate not found")] @@ -23,15 +21,11 @@ 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, + #[error("Too many parents")] + ParentOverflow, #[error("User not found, please contact technical support")] UserNotFoundBySessionId, - #[error("Crypto hash failed, please contact technical support")] - CryptoHashFailed, #[error("Crypto encryption failed, please contact technical support")] CryptoEncryptFailed, #[error("Crypto decryption failed, please contact technical support")] @@ -40,6 +34,8 @@ pub enum ServiceError { CandidateDetailsNotSet, #[error("Tokio join error")] TokioJoinError(#[from] tokio::task::JoinError), + #[error("Age no recipients error")] + AgeNoRecipientsError, #[error("Age encrypt error")] AgeEncryptError(#[from] age::EncryptError), #[error("Age decrypt error")] @@ -60,6 +56,8 @@ pub enum ServiceError { AesError(#[from] aes_gcm_siv::Error), #[error("Portfolio is incomplete")] IncompletePortfolio, + #[error("Portfolio write error")] + PortfolioWriteError, #[error("Zip error")] ZipError(#[from] async_zip::error::ZipError), #[error("Csv error")] @@ -71,23 +69,24 @@ pub enum ServiceError { impl ServiceError { pub fn code(&self) -> u16 { match self { + // 40X ServiceError::InvalidApplicationId => 400, - ServiceError::InvalidCredentials => 401, + ServiceError::ParentOverflow => 400, ServiceError::Unauthorized => 401, - ServiceError::Forbidden => 403, + ServiceError::InvalidCredentials => 401, ServiceError::ExpiredSession => 401, - ServiceError::JwtError => 500, - ServiceError::UserAlreadyExists => 409, + ServiceError::Forbidden => 403, ServiceError::CandidateNotFound => 404, + ServiceError::IncompletePortfolio => 406, + ServiceError::UserAlreadyExists => 409, + // 500 ServiceError::ParentNotFound => 500, - ServiceError::ParentOverflow => 400, // TODO: correct error code ServiceError::DbError(_) => 500, - ServiceError::UserNotFoundByJwtId => 500, ServiceError::UserNotFoundBySessionId => 500, - ServiceError::CryptoHashFailed => 500, ServiceError::CryptoEncryptFailed => 500, ServiceError::CryptoDecryptFailed => 500, ServiceError::CandidateDetailsNotSet => 500, + ServiceError::AgeNoRecipientsError => 500, ServiceError::AgeEncryptError(_) => 500, ServiceError::AgeDecryptError(_) => 500, ServiceError::AgeKeyError(_) => 500, @@ -98,25 +97,10 @@ impl ServiceError { ServiceError::TokioJoinError(_) => 500, ServiceError::AesError(_) => 500, ServiceError::ArgonConfigError(_) => 500, - //TODO: Correct code - ServiceError::IncompletePortfolio => 406, + ServiceError::PortfolioWriteError => 500, ServiceError::ZipError(_) => 500, ServiceError::CsvError(_) => 500, ServiceError::CsvIntoInnerError => 500, } } -} - -#[cfg(test)] -mod tests { - use super::ServiceError; - - #[test] - fn test_service_error_code() { - let error = ServiceError::CryptoHashFailed; - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status - assert!(error.code() >= 100); - assert!(error.code() <= 599); - } } \ No newline at end of file diff --git a/core/src/models/candidate.rs b/core/src/models/candidate.rs index 14c4082..a93e44a 100644 --- a/core/src/models/candidate.rs +++ b/core/src/models/candidate.rs @@ -30,11 +30,10 @@ pub struct BaseCandidateResponse { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "camelCase")] pub struct CandidateDetails { - // pub application_id: i32, pub name: String, pub surname: String, pub birthplace: String, - pub birthdate: NaiveDate, // TODO: User NaiveDate or String? + pub birthdate: NaiveDate, pub address: String, pub telephone: String, pub citizenship: String, @@ -46,7 +45,6 @@ pub struct CandidateDetails { #[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, @@ -65,7 +63,7 @@ pub struct ApplicationDetails { /// CSV export (admin endpoint) #[derive(FromQueryResult, Serialize, Default)] #[serde(rename_all = "camelCase")] -pub struct CandidateWithParent { // TODO: use this instead of (Candidate, Parent)??? +pub struct CandidateWithParent { pub application: i32, pub name: Option, pub surname: Option, diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index 3efcbaa..912e6c9 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -1,6 +1,7 @@ use chrono::NaiveDate; use entity::{candidate, parent}; +use futures::future; use crate::{crypto, models::candidate::{CandidateWithParent, ApplicationDetails}, error::ServiceError}; @@ -83,7 +84,6 @@ impl From for EncryptedString { } impl TryFrom> for EncryptedString { - // TODO: take a look at this type Error = ServiceError; fn try_from(d: Option) -> Result { @@ -97,20 +97,20 @@ impl TryFrom> for EncryptedString { impl EncryptedCandidateDetails { pub async fn new( form: &CandidateDetails, - recipients: Vec, + recipients: &Vec, ) -> Result { let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); let d = tokio::try_join!( - EncryptedString::new(&form.name, &recipients), - EncryptedString::new(&form.surname, &recipients), - EncryptedString::new(&form.birthplace, &recipients), - EncryptedString::new(&birthdate_str, &recipients), - EncryptedString::new(&form.address, &recipients), - EncryptedString::new(&form.telephone, &recipients), - EncryptedString::new(&form.citizenship, &recipients), - EncryptedString::new(&form.email, &recipients), - EncryptedString::new(&form.sex, &recipients), - EncryptedString::new(&form.personal_id_number, &recipients), + EncryptedString::new(&form.name, recipients), + EncryptedString::new(&form.surname, recipients), + EncryptedString::new(&form.birthplace, recipients), + EncryptedString::new(&birthdate_str, recipients), + EncryptedString::new(&form.address, recipients), + EncryptedString::new(&form.telephone, recipients), + EncryptedString::new(&form.citizenship, recipients), + EncryptedString::new(&form.email, recipients), + EncryptedString::new(&form.sex, recipients), + EncryptedString::new(&form.personal_id_number, recipients), )?; Ok( @@ -148,7 +148,7 @@ impl EncryptedCandidateDetails { name: d.0, surname: d.1, 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(), address: d.4, telephone: d.5, citizenship: d.6, @@ -187,13 +187,13 @@ impl TryFrom for EncryptedCandidateDetails { impl EncryptedParentDetails { pub async fn new( form: &ParentDetails, - recipients: Vec, + 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), + EncryptedString::new(&form.name, recipients), + EncryptedString::new(&form.surname, recipients), + EncryptedString::new(&form.telephone, recipients), + EncryptedString::new(&form.email, recipients), )?; Ok( @@ -244,13 +244,11 @@ impl EncryptedApplicationDetails { 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? - ); - } + let candidate = EncryptedCandidateDetails::new(&form.candidate, &recipients).await?; + let enc_parents = future::try_join_all( + form.parents.iter() + .map(|d| EncryptedParentDetails::new(d, &recipients)) + ).await?; Ok( EncryptedApplicationDetails { candidate, @@ -260,32 +258,31 @@ impl EncryptedApplicationDetails { } 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); - } + let decrypted_candidate = self.candidate.decrypt(priv_key.clone()).await?; + + let decrypted_parents = future::try_join_all( + self.parents + .iter() + .map(|d| d.decrypt(priv_key.clone())) + ).await?; + Ok(ApplicationDetails { - candidate, - parents: parents, + candidate: decrypted_candidate, + parents: decrypted_parents, }) } } -// TODO: use different metehod for this impl TryFrom<(candidate::Model, Vec)> for EncryptedApplicationDetails { type Error = ServiceError; fn try_from( (candidate, parents): (candidate::Model, Vec), ) -> Result { - let mut enc_parents = vec![]; - for parent in parents.iter() { - enc_parents.push( - EncryptedParentDetails::try_from(parent.clone())? - ); - } + let enc_parents = parents.iter() + .map(|m| EncryptedParentDetails::try_from(m.clone())) + .collect::, ServiceError>>()?; + Ok(EncryptedApplicationDetails { candidate: EncryptedCandidateDetails::try_from(candidate)?, parents: enc_parents, @@ -324,7 +321,6 @@ impl TryFrom for EncryptedApplicationDetails { } } -// TODO: use this more??? pub async fn decrypt_if_exists( private_key: &String, encrypted_string: Option, @@ -339,9 +335,12 @@ pub async fn decrypt_if_exists( pub mod tests { use std::sync::Mutex; + use chrono::{Local}; + use entity::admin; use once_cell::sync::Lazy; + use sea_orm::{DbConn, Set, ActiveModelTrait}; - use crate::{crypto, models::candidate::{CandidateDetails, ParentDetails}}; + use crate::{crypto, models::candidate::{CandidateDetails, ParentDetails}, utils::db::get_memory_sqlite_connection, Query, services::candidate_service::tests::put_user_data}; use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString}; @@ -384,10 +383,30 @@ pub mod tests { assert_eq!(details.candidate.sex, "sex"); assert_eq!(details.candidate.study, "study"); assert_eq!(details.candidate.personal_id_number, "personal_id_number"); - assert_eq!(details.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"); + for parent in &details.parents { + assert_eq!(parent.name, "parent_name"); + assert_eq!(parent.surname, "parent_surname"); + assert_eq!(parent.telephone, "parent_telephone"); + assert_eq!(parent.email, "parent_email"); + } + } + + async fn insert_test_admin(db: &DbConn) -> admin::Model { + admin::ActiveModel { + id: Set(1), + name: Set("Admin".to_owned()), + public_key: Set("age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_owned()), + // AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS + private_key: Set("5KCEGk0ueWVGnu5Xo3rmpLoilcVZ2ZWmwIcdZEJ8rrBNW7jwzZU/XTcTXtk/xyy/zjF8s+YnuVpOklQvX3EC/Sn+ZwyPY3jokM2RNwnZZlnqdehOEV1SMm/Y".to_owned()), + // test + password: Set("$argon2i$v=19$m=6000,t=3,p=10$WE9xCQmmWdBK82R4SEjoqA$TZSc6PuLd4aWK2x2WAb+Lm9sLySqjK3KLbNyqyQmzPQ".to_owned()), + created_at: Set(Local::now().naive_local()), + updated_at: Set(Local::now().naive_local()), + ..Default::default() + } + .insert(db) + .await + .unwrap() } #[tokio::test] @@ -436,35 +455,25 @@ pub mod tests { assert_all_application_details(&application_details); } - // TODO - /* #[tokio::test] + #[tokio::test] async fn test_encrypted_application_details_from_candidate_parent() { - const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; - const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; + let db = get_memory_sqlite_connection().await; + let _admin = insert_test_admin(&db).await; - const birthdate: NaiveDate = chrono::offset::Local::now().date_naive(); - let encrypted_details = EncryptedApplicationDetails::try_from( - , - vec![PUBLIC_KEY], - ) - .await - .unwrap(); + let (candidate, parents) = put_user_data(&db).await; + + let encrypted_details = EncryptedApplicationDetails::try_from((candidate, parents)).unwrap(); let application_details = encrypted_details - .decrypt(PRIVATE_KEY.to_string()) + .decrypt(PRIVATE_KEY.to_string()) // decrypt with admin's private key .await .unwrap(); assert_all_application_details(&application_details); - } */ + } #[tokio::test] async fn test_encrypted_string_new() { - const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; - const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; - let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()]) .await .unwrap(); @@ -479,10 +488,6 @@ pub mod tests { #[tokio::test] async fn test_encrypted_string_decrypt() { - const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; - const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; - let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()]) .await .unwrap(); diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index 88a737a..6c225e3 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -26,12 +26,13 @@ impl ApplicationService { db: &DbConn, candidate: candidate::Model, form: &ApplicationDetails, - ) -> Result<(candidate::Model, Vec), ServiceError> { // TODO: is this service needed? + ) -> Result<(candidate::Model, Vec), ServiceError> { + let recipients = get_recipients(db, &candidate.public_key).await?; Ok( ( - CandidateService::add_candidate_details(db, candidate.clone(), &form.candidate).await?, - ParentService::add_parents_details(db, candidate, &form.parents).await? + CandidateService::add_candidate_details(db, candidate.clone(), &form.candidate, &recipients).await?, + ParentService::add_parents_details(db, candidate, &form.parents, &recipients).await? ) ) } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index b15fea8..a22ea53 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -10,7 +10,7 @@ use crate::{ use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService, portfolio_service::PortfolioService}; -// TODO +// TODO validation /* pub struct FieldOfStudy { pub short_name: String, @@ -102,7 +102,7 @@ impl CandidateService { ) -> Result { let candidate = Query::find_candidate_by_id(db, id).await? .ok_or(ServiceError::CandidateNotFound)?; - let parents = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO + let parents = Query::find_candidate_parents(db, candidate.clone()).await?; let new_password_plain = crypto::random_8_char_string(); @@ -118,7 +118,6 @@ impl CandidateService { Mutation::update_candidate_password_and_keys(db, candidate.clone(), new_password_hash, pubkey, encrypted_priv_key).await?; // user might no have filled his details yet, but personal id number is filled from beginning - // TODO: make personal id number required let personal_id_number = EncryptedString::from(candidate.personal_identification_number.clone()) .decrypt(&admin_private_key) .await?; @@ -150,8 +149,8 @@ impl CandidateService { db: &DbConn, candidate: candidate::Model, details: &CandidateDetails, + recipients: &Vec, ) -> 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) @@ -243,7 +242,7 @@ impl CandidateService { fn is_application_id_valid(application_id: i32) -> bool { let s = &application_id.to_string(); if s.len() <= 3 { - // TODO: does the field of study prefix have to be exactly 6 digits? + // TODO: does the field of study prefix have to be exactly 6 digits? VYRESIT PODLE PRIHLASEK!!! return false; } let field_of_study_prefix = &s[0..3]; diff --git a/core/src/services/parent_service.rs b/core/src/services/parent_service.rs index f25664d..347292d 100644 --- a/core/src/services/parent_service.rs +++ b/core/src/services/parent_service.rs @@ -30,6 +30,7 @@ impl ParentService { db: &DbConn, ref_candidate: candidate::Model, parents_details: &Vec, + recipients: &Vec, ) -> Result, ServiceError> { let found_parents = Query::find_candidate_parents(db, ref_candidate.clone()).await?; if found_parents.len() > 2 { @@ -42,7 +43,6 @@ impl ParentService { 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); diff --git a/core/src/services/portfolio_service.rs b/core/src/services/portfolio_service.rs index 556f31c..2552154 100644 --- a/core/src/services/portfolio_service.rs +++ b/core/src/services/portfolio_service.rs @@ -314,6 +314,11 @@ impl PortfolioService { ).await?; tokio::fs::remove_file(final_path).await?; + + if !Self::is_portfolio_submitted(candidate_id).await { + return Err(ServiceError::PortfolioWriteError) + } + Ok(()) } diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs index 389c712..78bc054 100644 --- a/core/src/services/session_service.rs +++ b/core/src/services/session_service.rs @@ -109,7 +109,7 @@ impl SessionService { // delete old sessions SessionService::delete_old_sessions(db, user_id, admin_id, 3) .await - .ok(); // TODO move to dotenv + .ok(); Ok(session.id.to_string()) } diff --git a/core/src/utils/db.rs b/core/src/utils/db.rs index cc71e0e..c9a4503 100644 --- a/core/src/utils/db.rs +++ b/core/src/utils/db.rs @@ -1,6 +1,4 @@ -use entity::{admin, candidate, parent, session}; -use sea_orm::{Schema, Database, DbConn}; -use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; +use sea_orm::DbConn; use crate::Query; use crate::error::ServiceError; @@ -12,8 +10,12 @@ pub async fn get_recipients(db: &DbConn, candidate_pubkey: &str) -> Result sea_orm::DbConn { + use entity::{admin, candidate, parent, session}; + use sea_orm::{Schema, Database}; + use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; + + let base_url = "sqlite::memory:"; let db: DbConn = Database::connect(base_url).await.unwrap();