diff --git a/Cargo.lock b/Cargo.lock index 027ca82..03dcfad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2060,6 +2060,7 @@ dependencies = [ "dotenv", "futures", "infer", + "once_cell", "portfolio-entity", "rand 0.8.5", "sea-orm", diff --git a/api/src/guards/data/letter.rs b/api/src/guards/data/letter.rs index ed4f6e3..58ee4e2 100644 --- a/api/src/guards/data/letter.rs +++ b/api/src/guards/data/letter.rs @@ -31,7 +31,7 @@ impl<'r> FromData<'r> for Letter { let data_bytes = data_bytes.into_inner(); - let is_pdf = portfolio_core::filetype::filetype_is_pdf(&data_bytes); + let is_pdf = portfolio_core::utils::filetype::filetype_is_pdf(&data_bytes); if !is_pdf { // TODO: Not PDF diff --git a/api/src/guards/data/portfolio.rs b/api/src/guards/data/portfolio.rs index 623c589..9029220 100644 --- a/api/src/guards/data/portfolio.rs +++ b/api/src/guards/data/portfolio.rs @@ -31,7 +31,7 @@ impl<'r> FromData<'r> for Portfolio { let data_bytes = data_bytes.into_inner(); - let is_zip = portfolio_core::filetype::filetype_is_zip(&data_bytes); + let is_zip = portfolio_core::utils::filetype::filetype_is_zip(&data_bytes); if !is_zip { // TODO: Not ZIP diff --git a/api/src/lib.rs b/api/src/lib.rs index edd5814..b695ac0 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -43,12 +43,12 @@ pub fn rocket() -> Rocket{ routes::candidate::logout, routes::candidate::whoami, routes::candidate::get_details, + routes::candidate::post_details, ], ) .mount( "/candidate/add", routes![ - routes::candidate::add_details, routes::candidate::upload_portfolio_letter, routes::candidate::upload_portfolio_zip, routes::candidate::upload_cover_letter, diff --git a/api/src/pool.rs b/api/src/pool.rs index d3a45a6..539259c 100644 --- a/api/src/pool.rs +++ b/api/src/pool.rs @@ -23,7 +23,7 @@ impl sea_orm_rocket::Pool for SeaOrmPool { #[cfg(test)] async fn init(_figment: &Figment) -> Result { - let conn = portfolio_core::util::get_memory_sqlite_connection().await; + let conn = portfolio_core::utils::db::get_memory_sqlite_connection().await; crate::test::tests::run_test_migrations(&conn).await; return Ok(Self { conn }); } diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index 3700704..15f6823 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}, responses::CandidateResponse, candidate_details::ApplicationDetails, sea_orm::prelude::Uuid, + services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService, portfolio_service::PortfolioService}, responses::{BaseCandidateResponse, CreateCandidateResponse}, candidate_details::ApplicationDetails, sea_orm::prelude::Uuid, }; use requests::{AdminLoginRequest, RegisterRequest}; use rocket::http::{Cookie, Status, CookieJar}; @@ -11,7 +11,9 @@ use rocket::serde::json::Json; use sea_orm_rocket::Connection; -use crate::{guards::request::auth::AdminAuth, pool::Db, requests}; +use crate::{guards::request::{auth::AdminAuth}, pool::Db, requests}; + +use super::to_custom_error; #[post("/login", data = "")] pub async fn login( @@ -82,14 +84,14 @@ pub async fn hello(_session: AdminAuth) -> Result> { Ok("Hello admin".to_string()) } -#[post("/create", data = "")] +#[post("/create", data = "")] pub async fn create_candidate( conn: Connection<'_, Db>, _session: AdminAuth, - post_form: Json, -) -> Result> { + request: Json, +) -> Result, Custom> { let db = conn.into_inner(); - let form = post_form.into_inner(); + let form = request.into_inner(); let plain_text_password = random_8_char_string(); @@ -97,12 +99,20 @@ pub async fn create_candidate( db, form.application_id, &plain_text_password, - form.personal_id_number, + form.personal_id_number.clone(), ) .await - .map_err(|e| Custom(Status::InternalServerError, e.to_string()))?; + .map_err(to_custom_error)?; - Ok(plain_text_password) + Ok( + Json( + CreateCandidateResponse { + application_id: form.application_id, + personal_id_number: form.personal_id_number, + password: plain_text_password, + } + ) + ) } #[get("/candidates?&")] @@ -111,7 +121,7 @@ pub async fn list_candidates( session: AdminAuth, field: Option, page: Option, -) -> Result>, Custom> { +) -> Result>, Custom> { let db = conn.into_inner(); let private_key = session.get_private_key(); if let Some(field) = field.clone() { @@ -123,7 +133,7 @@ pub async fn list_candidates( let candidates = CandidateService::list_candidates(private_key, db, field, page) .await - .map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; + .map_err(to_custom_error)?; Ok(Json(candidates)) } @@ -143,7 +153,7 @@ pub async fn get_candidate( id ) .await - .map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; + .map_err(to_custom_error)?; Ok(Json(details)) } @@ -153,15 +163,17 @@ pub async fn reset_candidate_password( conn: Connection<'_, Db>, session: AdminAuth, id: i32, -) -> Result> { +) -> Result, Custom> { let db = conn.into_inner(); let private_key = session.get_private_key(); - let new_password = CandidateService::reset_password(private_key, db, id) + let response = CandidateService::reset_password(private_key, db, id) .await - .map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; + .map_err(to_custom_error)?; - Ok(new_password) + Ok( + Json(response) + ) } #[get("/candidate//portfolio")] @@ -173,13 +185,14 @@ pub async fn get_candidate_portfolio( let portfolio = PortfolioService::get_portfolio(id, private_key) .await - .map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; + .map_err(to_custom_error)?; Ok(portfolio) } #[cfg(test)] pub mod tests { + use portfolio_core::responses::CreateCandidateResponse; use rocket::{local::blocking::Client, http::{Cookie, Status}}; use crate::test::tests::{test_client, ADMIN_PASSWORD, ADMIN_ID}; @@ -208,7 +221,7 @@ pub mod tests { cookies: (Cookie, Cookie), id: i32, pid: String, - ) -> String { + ) -> CreateCandidateResponse { let response = client .post("/admin/create") .body(format!( @@ -224,15 +237,15 @@ pub mod tests { assert_eq!(response.status(), Status::Ok); - response.into_string().unwrap() + response.into_json::().unwrap() } #[test] fn test_create_candidate() { let client = test_client().lock().unwrap(); let cookies = admin_login(&client); - let password = create_candidate(&client, cookies, 1031511, "0".to_string()); + let response = create_candidate(&client, cookies, 1031511, "0".to_string()); - assert_eq!(password.len(), 8); + assert_eq!(response.password.len(), 8); } } \ No newline at end of file diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 6328b23..93b4d6f 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -16,6 +16,8 @@ use crate::guards::data::letter::Letter; use crate::guards::data::portfolio::Portfolio; use crate::{guards::request::auth::CandidateAuth, pool::Db, requests}; +use super::to_custom_error; + #[post("/login", data = "")] pub async fn login( conn: Connection<'_, Db>, @@ -25,31 +27,19 @@ pub async fn login( ) -> Result> { let ip_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let db = conn.into_inner(); - let session_token_key = CandidateService::login( + let (session_token, private_key) = CandidateService::login( db, login_form.application_id, login_form.password.to_string(), ip_addr.ip().to_string(), ) - .await; - - let Ok(session_token_key) = session_token_key else { - let e = session_token_key.unwrap_err(); - return Err(Custom( - Status::from_code(e.code()).unwrap_or(Status::InternalServerError), - e.to_string(), - )); - }; - - let session_token = session_token_key.0; - let private_key = session_token_key.1; + .await + .map_err(to_custom_error)?; cookies.add_private(Cookie::new("id", session_token.clone())); cookies.add_private(Cookie::new("key", private_key.clone())); - let response = format!("{} {}", session_token, private_key); - - return Ok(response); + return Ok("".to_string()); } #[post("/logout")] @@ -61,14 +51,14 @@ pub async fn logout(conn: Connection<'_, Db>, _session: CandidateAuth, cookies: let session_id = Uuid::try_parse(cookie.value()) // unwrap would be safe here because of the auth guard .map_err(|e| Custom(Status::BadRequest, e.to_string()))?; - let res = CandidateService::logout(db, session_id) + CandidateService::logout(db, session_id) .await - .map_err(|e| Custom(Status::from_code(e.code()).unwrap_or(Status::InternalServerError), e.to_string()))?; + .map_err(to_custom_error)?; cookies.remove_private(Cookie::named("id")); cookies.remove_private(Cookie::named("key")); - Ok(res) + Ok(()) } #[get("/whoami")] @@ -77,32 +67,27 @@ pub async fn whoami(session: CandidateAuth) -> Result> { Ok(candidate.application.to_string()) } +// TODO: use put instead of post??? #[post("/details", data = "
")] -pub async fn add_details( +pub async fn post_details( conn: Connection<'_, Db>, details: Json, session: CandidateAuth, -) -> Result> { +) -> Result, Custom> { let db = conn.into_inner(); let form = details.into_inner(); - let candidate: entity::candidate::Model = session.into(); // TODO: don't return candidate from session + let candidate: entity::candidate::Model = session.into(); - let candidate_parent = - ApplicationService::add_all_details(db, candidate.application, form).await; + let _candidate_parent = ApplicationService::add_all_details(db, candidate.application, &form) + .await + .map_err(to_custom_error)?; - if candidate_parent.is_err() { - // TODO cleanup - let e = candidate_parent.err().unwrap(); - return Err(Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - )); - } - - Ok("Details added".to_string()) + Ok( + Json(form) + ) } -#[post("/get_details")] +#[get("/details")] pub async fn get_details( conn: Connection<'_, Db>, session: CandidateAuth, @@ -114,14 +99,10 @@ pub async fn get_details( // let handle = tokio::spawn(async move { let details = ApplicationService::decrypt_all_details(private_key, db, candidate.application) .await - .map_err(|e| { - Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - ) - }); + .map(|x| Json(x)) + .map_err(to_custom_error); - details.map(|d| Json(d)) + details } #[post("/cover_letter", data = "")] pub async fn upload_cover_letter( @@ -130,40 +111,25 @@ pub async fn upload_cover_letter( ) -> Result> { let candidate: entity::candidate::Model = session.into(); - let candidate = - PortfolioService::add_cover_letter_to_cache(candidate.application, letter.into()).await; - - if candidate.is_err() { - // TODO cleanup - let e = candidate.err().unwrap(); - return Err(Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - )); - } + PortfolioService::add_cover_letter_to_cache(candidate.application, letter.into()) + .await + .map_err(to_custom_error)?; Ok("Letter added".to_string()) } #[get("/submission_progress")] pub async fn submission_progress( - conn: Connection<'_, Db>, session: CandidateAuth ) -> Result, Custom> { let candidate: entity::candidate::Model = session.into(); let progress = PortfolioService::get_submission_progress(candidate.application) .await - .map_err(|e| { - Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - ) - })?; + .map(|x| Json(x)) + .map_err(to_custom_error); - Ok( - Json(progress) - ) + progress } // TODO: JSON #[get["/is_cover_letter"]] @@ -182,17 +148,9 @@ pub async fn upload_portfolio_letter( ) -> Result> { let candidate: entity::candidate::Model = session.into(); - let candidate = - PortfolioService::add_portfolio_letter_to_cache(candidate.application, letter.into()).await; - - if candidate.is_err() { - // TODO cleanup - let e = candidate.err().unwrap(); - return Err(Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - )); - } + PortfolioService::add_portfolio_letter_to_cache(candidate.application, letter.into()) + .await + .map_err(to_custom_error)?; Ok("Letter added".to_string()) } @@ -214,17 +172,9 @@ pub async fn upload_portfolio_zip( ) -> Result> { let candidate: entity::candidate::Model = session.into(); - let candidate = - PortfolioService::add_portfolio_zip_to_cache(candidate.application, portfolio.into()).await; - - if candidate.is_err() { - // TODO cleanup - let e = candidate.err().unwrap(); - return Err(Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - )); - } + PortfolioService::add_portfolio_zip_to_cache(candidate.application, portfolio.into()) + .await + .map_err(to_custom_error)?; Ok("Portfolio added".to_string()) } @@ -256,19 +206,15 @@ pub async fn submit_portfolio( // TODO: VĂ­ce kontrol? if e.code() == 500 { // Cleanup - PortfolioService::delete_portfolio(candidate.application) - .await - .unwrap(); + PortfolioService::delete_portfolio(candidate.application).await.unwrap(); } - return Err(Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - )); + return Err(to_custom_error(e)); } Ok("Portfolio submitted".to_string()) } +#[deprecated = "Use /submission_progress instead"] #[get("/is_prepared")] pub async fn is_portfolio_prepared(session: CandidateAuth) -> Result> { let candidate: entity::candidate::Model = session.into(); @@ -286,6 +232,7 @@ pub async fn is_portfolio_prepared(session: CandidateAuth) -> Result Result> { let candidate: entity::candidate::Model = session.into(); @@ -308,17 +255,11 @@ pub async fn download_portfolio(session: CandidateAuth) -> Result, Custo let private_key = session.get_private_key(); let candidate: entity::candidate::Model = session.into(); - let file = PortfolioService::get_portfolio(candidate.application, private_key).await; + let file = PortfolioService::get_portfolio(candidate.application, private_key) + .await + .map_err(to_custom_error); - if file.is_err() { - let e = file.err().unwrap(); - return Err(Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.to_string(), - )); - } - - Ok(file.unwrap()) + file } #[cfg(test)] @@ -359,6 +300,7 @@ mod tests { \"citizenship\": \"Czech Republic\", \"email\": \"magor@magor.cz\", \"sex\": \"MALE\", + \"personal_id_number\": \"0000000000\", \"study\": \"KB\", \"parent_name\": \"maminka\", \"parent_surname\": \"chad\", @@ -394,7 +336,7 @@ mod tests { let details_orig: ApplicationDetails = serde_json::from_str(CANDIDATE_DETAILS).unwrap(); let response = client - .post("/candidate/add/details") + .post("/candidate/details") .cookie(cookies.0.clone()) .cookie(cookies.1.clone()) .body(CANDIDATE_DETAILS.to_string()) @@ -403,7 +345,7 @@ mod tests { assert_eq!(response.status(), Status::Ok); let response = client - .post("/candidate/get_details") + .get("/candidate/details") .cookie(cookies.0) .cookie(cookies.1) .dispatch(); @@ -424,7 +366,7 @@ mod tests { let key = Cookie::new("key", private_key); let response = client - .post("/candidate/add/details") + .post("/candidate/details") .cookie(id.clone()) .cookie(key.clone()) .body(CANDIDATE_DETAILS.to_string()) @@ -432,7 +374,7 @@ mod tests { assert_eq!(response.status(), Status::Unauthorized); let response = client - .post("/candidate/get_details") + .get("/candidate/details") .cookie(id.clone()) .cookie(key.clone()) .dispatch(); @@ -452,7 +394,7 @@ mod tests { let cookies = admin_login(&client); let response = client - .post("/candidate/add/details") + .post("/candidate/details") .cookie(cookies.0.clone()) .cookie(cookies.1.clone()) .body(CANDIDATE_DETAILS.to_string()) @@ -460,7 +402,7 @@ mod tests { assert_eq!(response.status(), Status::Unauthorized); let response = client - .post("/candidate/get_details") + .get("/candidate/details") .cookie(cookies.0.clone()) .cookie(cookies.1.clone()) .dispatch(); diff --git a/api/src/routes/mod.rs b/api/src/routes/mod.rs index 90ca91f..24af610 100644 --- a/api/src/routes/mod.rs +++ b/api/src/routes/mod.rs @@ -1,2 +1,12 @@ +use portfolio_core::error::ServiceError; +use rocket::{response::status::Custom, http::Status}; + pub mod admin; -pub mod candidate; \ No newline at end of file +pub mod candidate; + +pub fn to_custom_error(e: ServiceError) -> Custom { + Custom( + Status::from_code(e.code()).unwrap_or_default(), + e.to_string() + ) +} \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index a4ec0d4..7ddcf93 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -52,3 +52,4 @@ features = [ tokio = { version = "^1.22", features = ["macros"] } async-tempfile = "^0.2" serial_test = "0.9.0" +once_cell = "1.9.0" \ No newline at end of file diff --git a/core/src/candidate_details.rs b/core/src/candidate_details.rs index 29c40e6..0f7db19 100644 --- a/core/src/candidate_details.rs +++ b/core/src/candidate_details.rs @@ -11,7 +11,8 @@ pub const NAIVE_DATE_FMT: &str = "%Y-%m-%d"; pub struct EncryptedString(String); impl EncryptedString { - pub async fn new(s: &str, recipients: &Vec<&str>) -> Result { + pub async fn new(s: &str, recipients: &Vec) -> Result { + let recipients = recipients.iter().map(|s| &**s).collect(); match crypto::encrypt_password_with_recipients(&s, &recipients).await { Ok(encrypted) => Ok(Self(encrypted)), Err(_) => Err(ServiceError::CryptoEncryptFailed), @@ -46,6 +47,11 @@ impl TryFrom> for EncryptedString { } } } +impl From for EncryptedString { + fn from(s: String) -> Self { + Self(s) + } +} impl TryFrom> for EncryptedString { // TODO: take a look at this @@ -71,6 +77,7 @@ pub struct EncryptedApplicationDetails { pub citizenship: EncryptedString, pub email: EncryptedString, pub sex: EncryptedString, + pub personal_id_number: EncryptedString, pub study: String, // Parent @@ -82,8 +89,8 @@ pub struct EncryptedApplicationDetails { impl EncryptedApplicationDetails { pub async fn new( - form: ApplicationDetails, - recipients: Vec<&str>, + form: &ApplicationDetails, + recipients: Vec, ) -> Result { let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); let d = tokio::try_join!( @@ -96,6 +103,7 @@ impl EncryptedApplicationDetails { 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.parent_name, &recipients), EncryptedString::new(&form.parent_surname, &recipients), @@ -113,30 +121,32 @@ impl EncryptedApplicationDetails { citizenship: d.6, email: d.7, sex: d.8, - study: form.study, + personal_id_number: d.9, + study: form.study.clone(), - parent_name: d.9, - parent_surname: d.10, - parent_telephone: d.11, - parent_email: d.12, + parent_name: d.10, + parent_surname: d.11, + parent_telephone: d.12, + parent_email: d.13, }) } pub async fn decrypt(self, priv_key: String) -> Result { let d = tokio::try_join!( - self.name.decrypt(&priv_key), // 0 - self.surname.decrypt(&priv_key), // 1 - self.birthplace.decrypt(&priv_key), // 2 - self.birthdate.decrypt(&priv_key), // 3 - self.address.decrypt(&priv_key), // 4 - self.telephone.decrypt(&priv_key), // 5 - self.citizenship.decrypt(&priv_key), // 6 - self.email.decrypt(&priv_key), // 7 - self.sex.decrypt(&priv_key), // 8 - self.parent_name.decrypt(&priv_key), - self.parent_surname.decrypt(&priv_key), - self.parent_telephone.decrypt(&priv_key), - self.parent_email.decrypt(&priv_key), + self.name.decrypt(&priv_key), // 0 + self.surname.decrypt(&priv_key), // 1 + self.birthplace.decrypt(&priv_key), // 2 + self.birthdate.decrypt(&priv_key), // 3 + self.address.decrypt(&priv_key), // 4 + self.telephone.decrypt(&priv_key), // 5 + self.citizenship.decrypt(&priv_key), // 6 + self.email.decrypt(&priv_key), // 7 + self.sex.decrypt(&priv_key), // 8 + self.personal_id_number.decrypt(&priv_key),// 9 + self.parent_name.decrypt(&priv_key), // 10 + self.parent_surname.decrypt(&priv_key), // 11 + self.parent_telephone.decrypt(&priv_key), // 12 + self.parent_email.decrypt(&priv_key), // 13 )?; Ok(ApplicationDetails { @@ -149,12 +159,13 @@ impl EncryptedApplicationDetails { citizenship: d.6, email: d.7, sex: d.8, + personal_id_number: d.9, study: self.study, - parent_name: d.9, - parent_surname: d.10, - parent_telephone: d.11, - parent_email: d.12, + parent_name: d.10, + parent_surname: d.11, + parent_telephone: d.12, + parent_email: d.13, }) } } @@ -175,6 +186,7 @@ impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails 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)?, @@ -201,6 +213,7 @@ impl TryFrom for EncryptedApplicationDetails { citizenship: EncryptedString::try_from(cp.citizenship)?, email: EncryptedString::try_from(cp.email)?, sex: EncryptedString::try_from(cp.sex)?, + personal_id_number: EncryptedString::try_from(cp.personal_identification_number)?, study: cp.study.ok_or(ServiceError::CandidateDetailsNotSet)?, parent_name: EncryptedString::try_from(cp.parent_name)?, @@ -213,7 +226,7 @@ impl TryFrom for EncryptedApplicationDetails { -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ApplicationDetails { // Candidate pub name: String, @@ -226,7 +239,7 @@ pub struct ApplicationDetails { pub email: String, pub sex: String, pub study: String, - + pub personal_id_number: String, // Parent pub parent_name: String, pub parent_surname: String, @@ -235,34 +248,61 @@ pub struct ApplicationDetails { } #[cfg(test)] -mod tests { +pub mod tests { + use std::sync::Mutex; + + use once_cell::sync::Lazy; + use crate::crypto; use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString}; + const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; + const PRIVATE_KEY: &str = "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; + + 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() + }) + ); + + 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"); + } + #[tokio::test] async fn test_encrypted_application_details_new() { - const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; - const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; let encrypted_details = EncryptedApplicationDetails::new( - ApplicationDetails { - name: "test".to_string(), - surname: "test".to_string(), - birthplace: "test".to_string(), - birthdate: chrono::offset::Local::now().date_naive(), - address: "test".to_string(), - telephone: "test".to_string(), - citizenship: "test".to_string(), - email: "test".to_string(), - parent_email: "test".to_string(), - parent_name: "test".to_string(), - parent_surname: "test".to_string(), - parent_telephone: "test".to_string(), - sex: "test".to_string(), - study: "test".to_string(), - }, - vec![PUBLIC_KEY], + &APPLICATION_DETAILS.lock().unwrap().clone(), + vec![PUBLIC_KEY.to_string()], ) .await .unwrap(); @@ -271,44 +311,49 @@ mod tests { crypto::decrypt_password_with_private_key(&encrypted_details.name.0, PRIVATE_KEY) .await .unwrap(), - "test" + "name" ); assert_eq!( crypto::decrypt_password_with_private_key(&encrypted_details.email.0, PRIVATE_KEY) .await .unwrap(), - "test" + "email" ); assert_eq!( crypto::decrypt_password_with_private_key(&encrypted_details.sex.0, PRIVATE_KEY) .await .unwrap(), - "test" + "sex" ); } #[tokio::test] async fn test_encrypted_application_details_decrypt() { + let encrypted_details = EncryptedApplicationDetails::new( + &APPLICATION_DETAILS.lock().unwrap().clone(), + vec![PUBLIC_KEY.to_string()], + ) + .await + .unwrap(); + + let application_details = encrypted_details + .decrypt(PRIVATE_KEY.to_string()) + .await + .unwrap(); + + assert_all_application_details(&application_details); + } + + // TODO + /* #[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 encrypted_details = EncryptedApplicationDetails::new( - ApplicationDetails { - name: "test".to_string(), - surname: "test".to_string(), - birthplace: "test".to_string(), - birthdate: chrono::offset::Local::now().date_naive(), - address: "test".to_string(), - telephone: "test".to_string(), - citizenship: "test".to_string(), - email: "test".to_string(), - parent_email: "test".to_string(), - parent_name: "test".to_string(), - parent_surname: "test".to_string(), - parent_telephone: "test".to_string(), - sex: "test".to_string(), - study: "test".to_string(), - }, + + const birthdate: NaiveDate = chrono::offset::Local::now().date_naive(); + let encrypted_details = EncryptedApplicationDetails::try_from( + , vec![PUBLIC_KEY], ) .await @@ -319,10 +364,8 @@ mod tests { .await .unwrap(); - assert_eq!(application_details.name, "test"); - assert_eq!(application_details.email, "test"); - assert_eq!(application_details.sex, "test"); - } + assert_all_application_details(&application_details); + } */ #[tokio::test] async fn test_encrypted_string_new() { @@ -330,7 +373,7 @@ mod tests { const PRIVATE_KEY: &str = "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; - let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY]) + let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()]) .await .unwrap(); @@ -348,7 +391,7 @@ mod tests { const PRIVATE_KEY: &str = "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; - let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY]) + let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()]) .await .unwrap(); diff --git a/core/src/database/mutation/candidate.rs b/core/src/database/mutation/candidate.rs index 3fbc113..dbc2f55 100644 --- a/core/src/database/mutation/candidate.rs +++ b/core/src/database/mutation/candidate.rs @@ -8,13 +8,13 @@ impl Mutation { db: &DbConn, application_id: i32, hashed_password: String, - hashed_personal_id_number: String, + enc_personal_id_number: String, pubkey: String, encrypted_priv_key: String, ) -> Result { candidate::ActiveModel { application: Set(application_id), - personal_identification_number_hash: Set(hashed_personal_id_number), + personal_identification_number: Set(enc_personal_id_number), code: Set(hashed_password), public_key: Set(pubkey), private_key: Set(encrypted_priv_key), @@ -26,7 +26,7 @@ impl Mutation { .await } - pub async fn update_candidate_password_with_keys( + pub async fn update_candidate_password_and_keys( db: &DbConn, candidate: candidate::Model, new_password_hash: String, @@ -56,6 +56,7 @@ impl Mutation { 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.updated_at = Set(chrono::offset::Local::now().naive_local()); @@ -66,8 +67,9 @@ impl Mutation { #[cfg(test)] mod tests { - use crate::candidate_details::{ApplicationDetails, EncryptedApplicationDetails}; - use crate::util::get_memory_sqlite_connection; + use crate::candidate_details::tests::APPLICATION_DETAILS; + use crate::candidate_details::{EncryptedApplicationDetails}; + use crate::utils::db::get_memory_sqlite_connection; use crate::{Mutation, Query}; #[tokio::test] @@ -111,23 +113,8 @@ mod tests { .unwrap(); let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new( - ApplicationDetails { - name: "test".to_string(), - surname: "test".to_string(), - birthplace: "test".to_string(), - birthdate: chrono::offset::Local::now().date_naive(), - address: "test".to_string(), - telephone: "test".to_string(), - citizenship: "test".to_string(), - email: "test".to_string(), - parent_email: "test".to_string(), - parent_name: "test".to_string(), - parent_surname: "test".to_string(), - parent_telephone: "test".to_string(), - sex: "test".to_string(), - study: "test".to_string(), - }, - vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"], + &APPLICATION_DETAILS.lock().unwrap().clone(), + vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()], ).await.unwrap(); Mutation::add_candidate_details(&db, candidate, encrypted_details).await.unwrap(); diff --git a/core/src/database/mutation/parent.rs b/core/src/database/mutation/parent.rs index 1cfd01d..c31298c 100644 --- a/core/src/database/mutation/parent.rs +++ b/core/src/database/mutation/parent.rs @@ -34,8 +34,9 @@ impl Mutation { #[cfg(test)] mod tests { - use crate::candidate_details::{ApplicationDetails, EncryptedApplicationDetails}; - use crate::util::get_memory_sqlite_connection; + use crate::candidate_details::tests::APPLICATION_DETAILS; + use crate::candidate_details::{EncryptedApplicationDetails}; + use crate::utils::db::get_memory_sqlite_connection; use crate::{Mutation, Query}; #[tokio::test] @@ -81,23 +82,8 @@ mod tests { let parent = Mutation::create_parent(&db, APPLICATION_ID).await.unwrap(); let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new( - ApplicationDetails { - name: "test".to_string(), - surname: "test".to_string(), - birthplace: "test".to_string(), - birthdate: chrono::offset::Local::now().date_naive(), - address: "test".to_string(), - telephone: "test".to_string(), - citizenship: "test".to_string(), - email: "test".to_string(), - parent_email: "test".to_string(), - parent_name: "test".to_string(), - parent_surname: "test".to_string(), - parent_telephone: "test".to_string(), - sex: "test".to_string(), - study: "test".to_string(), - }, - vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"], + &APPLICATION_DETAILS.lock().unwrap().clone(), + vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()], ) .await .unwrap(); diff --git a/core/src/database/query/admin.rs b/core/src/database/query/admin.rs index de61db1..307d13f 100644 --- a/core/src/database/query/admin.rs +++ b/core/src/database/query/admin.rs @@ -25,7 +25,7 @@ mod tests { use entity::admin; use sea_orm::{ActiveModelTrait, Set}; - use crate::util::get_memory_sqlite_connection; + use crate::utils::db::get_memory_sqlite_connection; use crate::Query; #[tokio::test] diff --git a/core/src/database/query/candidate.rs b/core/src/database/query/candidate.rs index 4a696dd..ae24441 100644 --- a/core/src/database/query/candidate.rs +++ b/core/src/database/query/candidate.rs @@ -122,7 +122,7 @@ mod tests { use entity::candidate; use crate::Query; - use crate::util::get_memory_sqlite_connection; + use crate::utils::db::get_memory_sqlite_connection; #[tokio::test] async fn test_find_candidate_by_id() { @@ -132,7 +132,7 @@ 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() @@ -141,7 +141,7 @@ mod tests { .await .unwrap(); - let candidate = Query::find_candidate_by_id(&db, candidate.application) + let candidate = Query::find_candidate_by_id(&db, candidate.application) .await .unwrap(); assert!(candidate.is_some()); diff --git a/core/src/database/query/parent.rs b/core/src/database/query/parent.rs index 665d9de..787b85c 100644 --- a/core/src/database/query/parent.rs +++ b/core/src/database/query/parent.rs @@ -22,7 +22,7 @@ mod tests { use sea_orm::{ActiveModelTrait, Set}; use crate::Query; - use crate::util::get_memory_sqlite_connection; + use crate::utils::db::get_memory_sqlite_connection; #[tokio::test] async fn test_find_parent_by_id() { @@ -35,7 +35,7 @@ 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() diff --git a/core/src/database/query/session.rs b/core/src/database/query/session.rs index 6a1eb14..10aed95 100644 --- a/core/src/database/query/session.rs +++ b/core/src/database/query/session.rs @@ -35,7 +35,7 @@ mod tests { use entity::{session}; use sea_orm::{prelude::Uuid, ActiveModelTrait, Set}; - use crate::util::get_memory_sqlite_connection; + use crate::utils::db::get_memory_sqlite_connection; use crate::Query; #[tokio::test] diff --git a/core/src/lib.rs b/core/src/lib.rs index dd3e5c8..e58447a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -5,11 +5,9 @@ pub use database::query::*; pub mod database; pub mod crypto; -pub mod filetype; pub mod services; pub mod error; pub mod candidate_details; -pub mod util; pub mod responses; pub mod utils; diff --git a/core/src/responses.rs b/core/src/responses.rs index c0b7e5b..a1ccf56 100644 --- a/core/src/responses.rs +++ b/core/src/responses.rs @@ -1,9 +1,16 @@ -use serde::Serialize; +use serde::{Serialize, Deserialize}; use crate::{candidate_details::EncryptedString, error::ServiceError}; +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateCandidateResponse { + pub application_id: i32, + pub personal_id_number: String, + pub password: String, +} + #[derive(Debug, Serialize)] -pub struct CandidateResponse { +pub struct BaseCandidateResponse { pub application_id: i32, pub name: String, pub surname: String, @@ -11,7 +18,7 @@ pub struct CandidateResponse { pub submitted: bool, } -impl CandidateResponse { +impl BaseCandidateResponse { pub async fn from_encrypted( private_key: &String, application_id: i32, diff --git a/core/src/services/admin_service.rs b/core/src/services/admin_service.rs index fd7a62a..f393102 100644 --- a/core/src/services/admin_service.rs +++ b/core/src/services/admin_service.rs @@ -57,7 +57,7 @@ mod admin_tests { use entity::admin; use sea_orm::{Set, ActiveModelTrait}; - use crate::{util::get_memory_sqlite_connection, error::ServiceError}; + use crate::{utils::db::get_memory_sqlite_connection, error::ServiceError}; use super::*; diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index eec9931..e245a4b 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -1,7 +1,7 @@ use entity::{candidate, parent}; use sea_orm::DbConn; -use crate::{error::ServiceError, candidate_details::{ApplicationDetails, EncryptedApplicationDetails}, Query}; +use crate::{error::ServiceError, candidate_details::{ApplicationDetails, EncryptedApplicationDetails}, Query, utils::db::get_recipients}; use super::{parent_service::ParentService, candidate_service::CandidateService}; @@ -31,7 +31,7 @@ impl ApplicationService { pub async fn add_all_details( db: &DbConn, application: i32, - form: ApplicationDetails, + form: &ApplicationDetails, ) -> Result<(candidate::Model, parent::Model), ServiceError> { let candidate = Query::find_candidate_by_id(db, application) .await? @@ -41,13 +41,7 @@ impl ApplicationService { .await? .ok_or(ServiceError::ParentNotFound)?; - let admin_public_keys = Query::get_all_admin_public_keys(db).await?; - - let mut admin_public_keys_refrence: Vec<&str> = - admin_public_keys.iter().map(|s| &**s).collect(); - - let mut recipients = vec![&*candidate.public_key]; - recipients.append(&mut admin_public_keys_refrence); + let recipients = get_recipients(db, &candidate.public_key).await?; let enc_details = EncryptedApplicationDetails::new(form, recipients).await?; diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index a62a9e6..34cd9b5 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,16 +1,14 @@ -use std::path::{Path, PathBuf}; - use entity::candidate; use sea_orm::{prelude::Uuid, DbConn}; use crate::{ - candidate_details::{EncryptedApplicationDetails}, + candidate_details::{EncryptedApplicationDetails, EncryptedString}, crypto::{self, hash_password}, error::ServiceError, - Mutation, Query, responses::CandidateResponse, + Mutation, Query, responses::{BaseCandidateResponse, CreateCandidateResponse}, utils::db::get_recipients, }; -use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService}; +use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService, portfolio_service::PortfolioService}; // TODO @@ -45,12 +43,6 @@ const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"]; pub struct CandidateService; impl CandidateService { - // Get root path or local directory - fn get_file_store_path() -> PathBuf { - dotenv::dotenv().ok(); - Path::new(&std::env::var("STORE_PATH").unwrap_or_else(|_| "".to_string())).to_path_buf() - } - /// Creates a new candidate with: /// Encrypted personal identification number /// Hashed password @@ -69,33 +61,37 @@ impl CandidateService { // Check if user with that application id already exists if Query::find_candidate_by_id(db, application_id) - .await - .unwrap() + .await? .is_some() { return Err(ServiceError::UserAlreadyExists); } + PortfolioService::create_user_dir(application_id).await?; + let hashed_password = hash_password(plain_text_password.to_string()).await?; - let (pubkey, priv_key_plain_text) = crypto::create_identity(); + let encrypted_priv_key = crypto::encrypt_password( + priv_key_plain_text, + plain_text_password.to_string() + ).await?; - let encrypted_priv_key = - crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await?; - - let hashed_personal_id_number = hash_password(personal_id_number).await?; - - tokio::fs::create_dir_all(Self::get_file_store_path().join(&application_id.to_string()).join("cache")).await?; + let recipients = get_recipients(db, &pubkey).await?; + let enc_personal_id_number = EncryptedString::new( + &personal_id_number, + &recipients, + ).await?; let candidate = Mutation::create_candidate( db, application_id, hashed_password, - hashed_personal_id_number, + enc_personal_id_number.to_string(), pubkey, encrypted_priv_key, ) - .await?; + .await?; + Ok(candidate) } @@ -103,7 +99,7 @@ impl CandidateService { admin_private_key: String, db: &DbConn, id: i32, - ) -> Result { + ) -> 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? @@ -120,15 +116,30 @@ impl CandidateService { SessionService::revoke_all_sessions(db, Some(id), None).await?; - Mutation::update_candidate_password_with_keys(db, candidate.clone(), new_password_hash, pubkey, encrypted_priv_key).await?; + 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?; + + let enc_details_opt = EncryptedApplicationDetails::try_from( + (candidate, parent) + ); - let enc_details_opt = EncryptedApplicationDetails::try_from((candidate, parent)); 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, id, &application_details).await?; } - Ok(new_password_plain) + Ok( + CreateCandidateResponse { + application_id: id, + personal_id_number: personal_id_number, + password: new_password_plain, + } + ) } pub async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> { @@ -150,7 +161,7 @@ impl CandidateService { db: &DbConn, field_of_study: Option, page: Option, - ) -> Result, ServiceError> { + ) -> Result, ServiceError> { let candidates = Query::list_candidates( db, @@ -158,11 +169,11 @@ impl CandidateService { page ).await?; - let mut result: Vec = vec![]; + let mut result: Vec = vec![]; for candidate in candidates { result.push( - CandidateResponse::from_encrypted( + BaseCandidateResponse::from_encrypted( &private_key, candidate.application, candidate.name, @@ -212,14 +223,10 @@ impl CandidateService { let session_id = SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr) - .await; - match session_id { - Ok(session_id) => { - let private_key = Self::decrypt_private_key(candidate, password).await?; - Ok((session_id, private_key)) - } - Err(e) => Err(e), - } + .await?; + + let private_key = Self::decrypt_private_key(candidate, password).await?; + Ok((session_id, private_key)) } pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result { @@ -247,14 +254,13 @@ impl CandidateService { pub mod tests { use sea_orm::{DbConn}; - use crate::util::get_memory_sqlite_connection; + use crate::candidate_details::tests::assert_all_application_details; + use crate::utils::db::get_memory_sqlite_connection; use crate::{crypto, services::candidate_service::CandidateService, Mutation}; use super::EncryptedApplicationDetails; - use chrono::NaiveDate; use entity::{candidate, parent, admin}; - use crate::candidate_details::{ApplicationDetails}; use crate::services::application_service::ApplicationService; const APPLICATION_ID: i32 = 103151; @@ -282,7 +288,7 @@ pub mod tests { CandidateService::login(&db, candidate.application, "test".to_string(), "127.0.0.1".to_string()).await.is_ok() ); - let new_password = CandidateService::reset_password(private_key, &db, candidate.application).await.unwrap(); + let new_password = CandidateService::reset_password(private_key, &db, candidate.application).await.unwrap().password; assert!( CandidateService::login(&db, candidate.application, "test".to_string(), "127.0.0.1".to_string()).await.is_err() @@ -368,6 +374,8 @@ pub mod tests { #[cfg(test)] pub async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) { + use crate::candidate_details::tests::APPLICATION_DETAILS; + let plain_text_password = "test".to_string(); let (candidate, _parent) = ApplicationService::create_candidate_with_parent( &db, @@ -379,24 +387,9 @@ pub mod tests { .ok() .unwrap(); - let form = ApplicationDetails { - name: "name".to_string(), - surname: "surname".to_string(), - birthplace: "birthplace".to_string(), - birthdate: 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(), - study: "KB".to_string(), - parent_name: "parent_name".to_string(), - parent_surname: "parent_surname".to_string(), - parent_telephone: "parent_telephone".to_string(), - parent_email: "parent_email".to_string(), - }; + let form = APPLICATION_DETAILS.lock().unwrap().clone(); - ApplicationService::add_all_details(&db, candidate.application, form) + ApplicationService::add_all_details(&db, candidate.application, &form) .await .unwrap() } @@ -423,19 +416,6 @@ pub mod tests { .unwrap(); let dec_details = enc_details.decrypt(dec_priv_key).await.ok().unwrap(); - assert_eq!(dec_details.name, "name"); - assert_eq!(dec_details.surname, "surname"); - assert_eq!(dec_details.birthplace, "birthplace"); - assert_eq!(dec_details.birthdate, NaiveDate::from_ymd(2000, 1, 1)); - assert_eq!(dec_details.address, "address"); - assert_eq!(dec_details.telephone, "telephone"); - assert_eq!(dec_details.citizenship, "citizenship"); - assert_eq!(dec_details.email, "email"); - assert_eq!(dec_details.sex, "sex"); - assert_eq!(dec_details.study, "KB"); - assert_eq!(dec_details.parent_name, "parent_name"); - assert_eq!(dec_details.parent_surname, "parent_surname"); - assert_eq!(dec_details.parent_telephone, "parent_telephone"); - assert_eq!(dec_details.parent_email, "parent_email"); + assert_all_application_details(&dec_details); } } diff --git a/core/src/services/portfolio_service.rs b/core/src/services/portfolio_service.rs index 6917dca..89deb3b 100644 --- a/core/src/services/portfolio_service.rs +++ b/core/src/services/portfolio_service.rs @@ -145,6 +145,14 @@ impl PortfolioService { Ok(()) } + pub async fn create_user_dir(application_id: i32) -> tokio::io::Result<()> { + tokio::fs::create_dir_all( + Self::get_file_store_path() + .join(&application_id.to_string()) + .join("cache")) + .await + } + pub async fn add_cover_letter_to_cache( candidate_id: i32, @@ -324,7 +332,7 @@ impl PortfolioService { mod tests { use serial_test::serial; - use crate::{services::{portfolio_service::{PortfolioService, FileType}, candidate_service::{CandidateService, tests::put_user_data}}, util::get_memory_sqlite_connection, crypto}; + use crate::{services::{portfolio_service::{PortfolioService, FileType}, candidate_service::{CandidateService, tests::put_user_data}}, utils::db::get_memory_sqlite_connection, crypto}; use std::path::PathBuf; const APPLICATION_ID: i32 = 103151; diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs index 08c9bac..389c712 100644 --- a/core/src/services/session_service.rs +++ b/core/src/services/session_service.rs @@ -172,7 +172,7 @@ mod tests { use crate::{ crypto, services::{application_service::ApplicationService, session_service::SessionService}, - util::get_memory_sqlite_connection, + utils::db::get_memory_sqlite_connection, }; #[tokio::test] diff --git a/core/src/util.rs b/core/src/utils/db.rs similarity index 77% rename from core/src/util.rs rename to core/src/utils/db.rs index a988644..cc71e0e 100644 --- a/core/src/util.rs +++ b/core/src/utils/db.rs @@ -2,6 +2,16 @@ use entity::{admin, candidate, parent, session}; use sea_orm::{Schema, Database, DbConn}; use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; +use crate::Query; +use crate::error::ServiceError; + +pub async fn get_recipients(db: &DbConn, candidate_pubkey: &str) -> Result, ServiceError> { + let mut admin_public_keys = Query::get_all_admin_public_keys(db).await?; + let mut recipients = vec![candidate_pubkey.to_string()]; + recipients.append(&mut admin_public_keys); + Ok(recipients) +} + pub async fn get_memory_sqlite_connection() -> sea_orm::DbConn { let base_url = "sqlite::memory:"; diff --git a/core/src/filetype.rs b/core/src/utils/filetype.rs similarity index 100% rename from core/src/filetype.rs rename to core/src/utils/filetype.rs diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index a89587e..d498f2d 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -1 +1,3 @@ -pub mod csv; \ No newline at end of file +pub mod csv; +pub mod filetype; +pub mod db; \ No newline at end of file diff --git a/entity/src/candidate.rs b/entity/src/candidate.rs index 6b7d5bd..fdfbc46 100644 --- a/entity/src/candidate.rs +++ b/entity/src/candidate.rs @@ -19,9 +19,9 @@ pub struct Model { pub email: Option, pub sex: Option, pub study: Option, - pub personal_identification_number: Option, - #[sea_orm(column_type = "Text")] - pub personal_identification_number_hash: String, + pub personal_identification_number: String, + #[sea_orm(column_type = "Text", nullable)] + pub personal_identification_number_hash: Option, pub public_key: String, pub private_key: String, pub created_at: DateTime, @@ -30,16 +30,10 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { - #[sea_orm(has_many = "super::parent::Entity")] - Parent, #[sea_orm(has_many = "super::session::Entity")] Session, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Parent.def() - } + #[sea_orm(has_many = "super::parent::Entity")] + Parent, } impl Related for Entity { @@ -48,4 +42,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Parent.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/migration/src/m20221024_121621_create_candidate.rs b/migration/src/m20221024_121621_create_candidate.rs index 66d6466..89e0522 100644 --- a/migration/src/m20221024_121621_create_candidate.rs +++ b/migration/src/m20221024_121621_create_candidate.rs @@ -30,8 +30,8 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Candidate::Email).string()) .col(ColumnDef::new(Candidate::Sex).string()) .col(ColumnDef::new(Candidate::Study).string()) - .col(ColumnDef::new(Candidate::PersonalIdentificationNumber).string()) - .col(ColumnDef::new(Candidate::PersonalIdentificationNumberHash).text().not_null()) + .col(ColumnDef::new(Candidate::PersonalIdentificationNumber).string().not_null()) + .col(ColumnDef::new(Candidate::PersonalIdentificationNumberHash).text()) .col(ColumnDef::new(Candidate::PublicKey).string().not_null()) .col(ColumnDef::new(Candidate::PrivateKey).string().not_null()) .col(ColumnDef::new(Candidate::CreatedAt).date_time().not_null())