mirror of
https://github.com/danbulant/Portfolio
synced 2026-07-05 19:11:06 +00:00
feat: link candidates
This commit is contained in:
parent
61facc503c
commit
6e1c35f721
9 changed files with 110 additions and 19 deletions
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue