feat: link candidates

This commit is contained in:
Sebastian Pravda 2023-01-14 16:27:07 +01:00
parent 61facc503c
commit 6e1c35f721
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
9 changed files with 110 additions and 19 deletions

View file

@ -85,15 +85,16 @@ pub async fn hello(_session: AdminAuth) -> Result<String, Custom<String>> {
#[post("/create", data = "<request>")] #[post("/create", data = "<request>")]
pub async fn create_candidate( pub async fn create_candidate(
conn: Connection<'_, Db>, conn: Connection<'_, Db>,
_session: AdminAuth, session: AdminAuth,
request: Json<RegisterRequest>, request: Json<RegisterRequest>,
) -> Result<Json<CreateCandidateResponse>, Custom<String>> { ) -> Result<Json<CreateCandidateResponse>, Custom<String>> {
let db = conn.into_inner(); let db = conn.into_inner();
let form = request.into_inner(); let form = request.into_inner();
let private_key = session.get_private_key();
let plain_text_password = random_12_char_string(); let plain_text_password = random_12_char_string();
let application = ApplicationService::create(&db, form.application_id, &plain_text_password, form.personal_id_number.clone()) let application = ApplicationService::create(&private_key, &db, form.application_id, &plain_text_password, form.personal_id_number.clone())
.await .await
.map_err(to_custom_error)?; .map_err(to_custom_error)?;
@ -161,10 +162,12 @@ 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) let application = Query::find_application_by_id(db, id)
.await .await
.map_err(|e| to_custom_error(ServiceError::DbError(e)))? .map_err(|e| to_custom_error(ServiceError::DbError(e)))?
.ok_or(to_custom_error(ServiceError::CandidateNotFound))?; .ok_or(to_custom_error(ServiceError::CandidateNotFound))?;
let candidate = ApplicationService::find_related_candidate(db, application).await
.map_err(to_custom_error)?;
let details = ApplicationService::decrypt_all_details( let details = ApplicationService::decrypt_all_details(
private_key, private_key,
@ -187,10 +190,13 @@ pub async fn delete_candidate(
) -> Result<(), Custom<String>> { ) -> Result<(), Custom<String>> {
let db = conn.into_inner(); let db = conn.into_inner();
let candidate = Query::find_candidate_by_id(db, id) let application = Query::find_application_by_id(db, id)
.await .await
.map_err(|e| to_custom_error(ServiceError::DbError(e)))? .map_err(|e| to_custom_error(ServiceError::DbError(e)))?
.ok_or(to_custom_error(ServiceError::CandidateNotFound))?; .ok_or(to_custom_error(ServiceError::CandidateNotFound))?;
let candidate = ApplicationService::find_related_candidate(db, application).await.map_err(to_custom_error)?;
// TODO
CandidateService::delete_candidate(db, candidate) CandidateService::delete_candidate(db, candidate)
.await .await
@ -219,12 +225,19 @@ pub async fn reset_candidate_password(
#[get("/candidate/<id>/portfolio")] #[get("/candidate/<id>/portfolio")]
pub async fn get_candidate_portfolio( pub async fn get_candidate_portfolio(
conn: Connection<'_, Db>,
session: AdminAuth, session: AdminAuth,
id: i32, id: i32,
) -> Result<Vec<u8>, Custom<String>> { ) -> Result<Vec<u8>, Custom<String>> {
let db = conn.into_inner();
let private_key = session.get_private_key(); let private_key = session.get_private_key();
let portfolio = PortfolioService::get_portfolio(id, private_key) let application = Query::find_application_by_id(db, id)
.await
.map_err(|e| to_custom_error(ServiceError::DbError(e)))?
.ok_or(to_custom_error(ServiceError::CandidateNotFound))?;
let portfolio = PortfolioService::get_portfolio(application.candidate_id, private_key)
.await .await
.map_err(to_custom_error)?; .map_err(to_custom_error)?;

View file

@ -43,6 +43,7 @@ pub mod tests {
.unwrap(); .unwrap();
let application = ApplicationService::create( let application = ApplicationService::create(
&"".to_string(),
db, db,
APPLICATION_ID, APPLICATION_ID,
&CANDIDATE_PASSWORD.to_string(), &CANDIDATE_PASSWORD.to_string(),

View file

@ -1,6 +1,6 @@
use ::entity::application; use ::entity::application;
use log::{info, warn}; use log::{info, warn};
use sea_orm::{DbConn, DbErr, Set, ActiveModelTrait, EntityTrait, IntoActiveModel}; use sea_orm::{DbConn, DbErr, Set, ActiveModelTrait, EntityTrait, IntoActiveModel, QueryFilter, ColumnTrait};
use crate::Mutation; use crate::Mutation;

View file

@ -72,6 +72,20 @@ impl Mutation {
Ok(update) Ok(update)
} }
pub async fn update_personal_id(
db: &DbConn,
candidate: candidate::Model,
personal_id: &str,
) -> Result<candidate::Model, DbErr> {
let mut candidate = candidate.into_active_model();
candidate.personal_identification_number = Set(personal_id.to_string());
candidate
.update(db)
.await
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,5 +1,5 @@
use entity::{application, candidate}; use entity::{application, candidate};
use sea_orm::{EntityTrait, DbErr, DbConn, ModelTrait, FromQueryResult, QuerySelect, JoinType, RelationTrait}; use sea_orm::{EntityTrait, DbErr, DbConn, ModelTrait, FromQueryResult, QuerySelect, JoinType, RelationTrait, QueryFilter, ColumnTrait};
#[derive(FromQueryResult, Clone)] #[derive(FromQueryResult, Clone)]
pub struct ApplicationCandidateJoin { pub struct ApplicationCandidateJoin {
@ -52,4 +52,16 @@ impl Query {
.all(db) .all(db)
.await .await
} }
pub async fn find_applications_by_candidate_id(
db: &DbConn,
candidate_id: i32,
) -> Result<Vec<application::Model>, DbErr> {
let applications = application::Entity::find()
.filter(application::Column::CandidateId.eq(candidate_id))
.all(db)
.await?;
Ok(applications)
}
} }

View file

@ -6,6 +6,12 @@ use crate::Query;
pub const PAGE_SIZE: u64 = 20; pub const PAGE_SIZE: u64 = 20;
#[derive(FromQueryResult)]
pub struct IdPersonalIdNumberJoin {
pub id: i32,
pub personal_id_number: String,
}
#[derive(FromQueryResult)] #[derive(FromQueryResult)]
pub struct ApplicationId { pub struct ApplicationId {
application: i32, application: i32,
@ -82,6 +88,15 @@ impl Query {
.await .await
} }
pub async fn find_candidate_by_personal_id(
db: &DbConn,
personal_id: &str,
) -> Result<Option<candidate::Model>, DbErr> {
Candidate::find()
.filter(candidate::Column::PersonalIdentificationNumber.eq(personal_id))
.one(db)
.await
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -18,6 +18,7 @@ impl ApplicationService {
/// Encrypted private key /// Encrypted private key
/// Public key /// Public key
pub async fn create( pub async fn create(
admin_private_key: &String,
db: &DbConn, db: &DbConn,
application_id: i32, application_id: i32,
plain_text_password: &String, plain_text_password: &String,
@ -36,7 +37,6 @@ impl ApplicationService {
return Err(ServiceError::UserAlreadyExists); return Err(ServiceError::UserAlreadyExists);
} }
let hashed_password = hash_password(plain_text_password.to_string()).await?; let hashed_password = hash_password(plain_text_password.to_string()).await?;
let (pubkey, priv_key_plain_text) = crypto::create_identity(); let (pubkey, priv_key_plain_text) = crypto::create_identity();
let encrypted_priv_key = crypto::encrypt_password( let encrypted_priv_key = crypto::encrypt_password(
@ -50,13 +50,13 @@ impl ApplicationService {
&recipients, &recipients,
).await?; ).await?;
let candidate = CandidateService::create( let candidate = Self::find_or_create_candidate_with_personal_id(
admin_private_key,
db, db,
enc_personal_id_number.clone().to_string() personal_id_number,
&enc_personal_id_number,
).await?; ).await?;
println!("candidate: {:?}", candidate);
let application = Mutation::create_application( let application = Mutation::create_application(
db, db,
application_id, application_id,
@ -70,6 +70,41 @@ impl ApplicationService {
Ok(application) Ok(application)
} }
async fn find_or_create_candidate_with_personal_id(
admin_private_key: &String,
db: &DbConn,
personal_id_number: String,
enc_personal_id_number: &EncryptedString,
) -> Result<candidate::Model, ServiceError> {
let candidates = Query::list_candidates_full(db).await?;
let ids_decrypted = futures::future::join_all(
candidates.iter().map(|c| async {(
c.id,
EncryptedString::from(c.personal_identification_number.clone())
.decrypt(admin_private_key)
.await
.unwrap_or_default(),
)}
))
.await;
let found_ids: Vec<&(i32, String)> = ids_decrypted
.iter()
.filter(|(_, id)| id == &personal_id_number)
.collect();
// TODO: take the candidate id directly from the iterator
if found_ids.iter().any(|(_, personal_id)| personal_id == &personal_id_number) {
let candidate = Query::find_candidate_by_id(db, found_ids[0].0)
.await?
.ok_or(ServiceError::CandidateNotFound)?;
let candidate = Mutation::update_personal_id(db, candidate, &enc_personal_id_number.to_owned().to_string()).await?;
println!("Candidates linked!");
Ok(candidate)
} else {
CandidateService::create(db, enc_personal_id_number.to_owned().to_string()).await
}
}
fn is_application_id_valid(application_id: i32) -> bool { fn is_application_id_valid(application_id: i32) -> bool {
let s = &application_id.to_string(); let s = &application_id.to_string();
if s.len() <= 3 { if s.len() <= 3 {
@ -368,7 +403,7 @@ mod tests {
let secret_message = "trnka".to_string(); let secret_message = "trnka".to_string();
let application = ApplicationService::create(&db, 103100, &plain_text_password, "".to_string()).await.unwrap(); let application = ApplicationService::create(&"".to_string(), &db, 103100, &plain_text_password, "".to_string()).await.unwrap();
let encrypted_message = let encrypted_message =
crypto::encrypt_password_with_recipients(&secret_message, &vec![&application.public_key]) crypto::encrypt_password_with_recipients(&secret_message, &vec![&application.public_key])

View file

@ -141,6 +141,7 @@ pub mod tests {
let plain_text_password = "test".to_string(); let plain_text_password = "test".to_string();
let application = ApplicationService::create( let application = ApplicationService::create(
&"".to_string(),
db, db,
APPLICATION_ID, APPLICATION_ID,
&plain_text_password, &plain_text_password,

View file

@ -52,7 +52,7 @@ mod tests {
let db = get_memory_sqlite_connection().await; let db = get_memory_sqlite_connection().await;
let application = ApplicationService::create(&db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); let application = ApplicationService::create(&"".to_string(), &db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap();
assert_eq!(application.id.to_owned(), 103151); assert_eq!(application.id.to_owned(), 103151);
assert_ne!(application.password.to_owned(), SECRET.to_string()); assert_ne!(application.password.to_owned(), SECRET.to_string());
@ -66,7 +66,7 @@ mod tests {
async fn test_candidate_session_correct_password() { async fn test_candidate_session_correct_password() {
let db = &get_memory_sqlite_connection().await; let db = &get_memory_sqlite_connection().await;
let application = ApplicationService::create(&db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); let application = ApplicationService::create(&"".to_string(), &db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap();
// correct password // correct password
let session = ApplicationService::new_session( let session = ApplicationService::new_session(
@ -89,7 +89,7 @@ mod tests {
async fn test_candidate_session_incorrect_password() { async fn test_candidate_session_incorrect_password() {
let db = &get_memory_sqlite_connection().await; let db = &get_memory_sqlite_connection().await;
let application = ApplicationService::create(&db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); let application = ApplicationService::create(&"".to_string(), &db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap();
// incorrect password // incorrect password
assert!(ApplicationService::new_session( assert!(ApplicationService::new_session(