mirror of
https://github.com/danbulant/Portfolio
synced 2026-07-06 11:30:44 +00:00
feat: thiserror PoC
This commit is contained in:
parent
72ae61637f
commit
98c59694a7
8 changed files with 79 additions and 75 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1906,6 +1906,7 @@ dependencies = [
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"serde",
|
"serde",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ portfolio-entity = { path = "../entity" }
|
||||||
# serde
|
# serde
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
# error
|
||||||
|
|
||||||
|
thiserror = "^1.0"
|
||||||
|
|
||||||
# env
|
# env
|
||||||
dotenv = "^0.15"
|
dotenv = "^0.15"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,39 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
|
||||||
pub enum ServiceError {
|
pub enum ServiceError {
|
||||||
|
#[error("Invalid application id")]
|
||||||
InvalidApplicationId,
|
InvalidApplicationId,
|
||||||
|
#[error("Invalid credentials")]
|
||||||
InvalidCredentials,
|
InvalidCredentials,
|
||||||
|
#[error("Forbidden")]
|
||||||
Forbidden,
|
Forbidden,
|
||||||
|
#[error("Session expired, please login agai")]
|
||||||
ExpiredSession,
|
ExpiredSession,
|
||||||
|
#[error("Error while encoding JWT")]
|
||||||
JwtError,
|
JwtError,
|
||||||
|
#[error("User already exists")]
|
||||||
UserAlreadyExists,
|
UserAlreadyExists,
|
||||||
|
#[error("Candidate not found")]
|
||||||
CandidateNotFound,
|
CandidateNotFound,
|
||||||
|
#[error("Parrent not found")]
|
||||||
ParentNotFound,
|
ParentNotFound,
|
||||||
DbError,
|
#[error("Database error")]
|
||||||
|
DbError(#[from] sea_orm::DbErr),
|
||||||
|
#[error("User not found, please contact technical support")]
|
||||||
UserNotFoundByJwtId,
|
UserNotFoundByJwtId,
|
||||||
|
#[error("User not found, please contact technical support")]
|
||||||
UserNotFoundBySessionId,
|
UserNotFoundBySessionId,
|
||||||
|
#[error("Crypto hash failed, please contact technical support")]
|
||||||
CryptoHashFailed,
|
CryptoHashFailed,
|
||||||
|
#[error("Crypto encryption failed, please contact technical support")]
|
||||||
CryptoEncryptFailed,
|
CryptoEncryptFailed,
|
||||||
|
#[error("Crypto decryption failed, please contact technical support")]
|
||||||
CryptoDecryptFailed,
|
CryptoDecryptFailed,
|
||||||
|
#[error("Candidate details not set, please contact technical support")]
|
||||||
CandidateDetailsNotSet,
|
CandidateDetailsNotSet,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceError {
|
impl ServiceError {
|
||||||
|
|
@ -27,7 +47,7 @@ impl ServiceError {
|
||||||
ServiceError::UserAlreadyExists => (409, "User already exists".to_string()),
|
ServiceError::UserAlreadyExists => (409, "User already exists".to_string()),
|
||||||
ServiceError::CandidateNotFound => (404, "User not found".to_string()),
|
ServiceError::CandidateNotFound => (404, "User not found".to_string()),
|
||||||
ServiceError::ParentNotFound => (500, "Parent not found".to_string()),
|
ServiceError::ParentNotFound => (500, "Parent not found".to_string()),
|
||||||
ServiceError::DbError => (500, "Database error".to_string()),
|
ServiceError::DbError(_) => (500, "Database error".to_string()),
|
||||||
ServiceError::UserNotFoundByJwtId => (500, "User not found, please contact technical support".to_string()),
|
ServiceError::UserNotFoundByJwtId => (500, "User not found, please contact technical support".to_string()),
|
||||||
ServiceError::UserNotFoundBySessionId => (500, "User not found, please contact technical support".to_string()),
|
ServiceError::UserNotFoundBySessionId => (500, "User not found, please contact technical support".to_string()),
|
||||||
ServiceError::CryptoHashFailed => (500, "Crypto hash failed, please contact technical support".to_string()),
|
ServiceError::CryptoHashFailed => (500, "Crypto hash failed, please contact technical support".to_string()),
|
||||||
|
|
@ -45,15 +65,3 @@ impl ServiceError {
|
||||||
self.code_and_message().1
|
self.code_and_message().1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for ServiceError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "ServiceError {{ code: {}, message: {} }}", self.code(), self.message())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for ServiceError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "ServiceError {{ code: {}, message: {} }}", self.code(), self.message())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,11 +13,7 @@ impl AdminService {
|
||||||
admin_id: i32,
|
admin_id: i32,
|
||||||
password: String,
|
password: String,
|
||||||
) -> Result<String, ServiceError> {
|
) -> Result<String, ServiceError> {
|
||||||
let admin = Query::find_admin_by_id(db, admin_id).await;
|
let admin = Query::find_admin_by_id(db, admin_id).await?;
|
||||||
|
|
||||||
let Ok(admin) = admin else {
|
|
||||||
return Err(ServiceError::DbError);
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(admin) = admin else {
|
let Some(admin) = admin else {
|
||||||
return Err(ServiceError::CandidateNotFound);
|
return Err(ServiceError::CandidateNotFound);
|
||||||
|
|
@ -55,7 +51,7 @@ impl AdminService {
|
||||||
match SessionService::auth_user_session(db, session_uuid).await {
|
match SessionService::auth_user_session(db, session_uuid).await {
|
||||||
Ok(user) => match user {
|
Ok(user) => match user {
|
||||||
AdminUser::Admin(admin) => Ok(admin),
|
AdminUser::Admin(admin) => Ok(admin),
|
||||||
AdminUser::Candidate(_) => Err(ServiceError::DbError),
|
AdminUser::Candidate(_) => unreachable!(),
|
||||||
},
|
},
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,18 +34,14 @@ impl ApplicationService {
|
||||||
form: ApplicationDetails,
|
form: ApplicationDetails,
|
||||||
) -> Result<(candidate::Model, parent::Model), ServiceError> {
|
) -> Result<(candidate::Model, parent::Model), ServiceError> {
|
||||||
let candidate = Query::find_candidate_by_id(db, application)
|
let candidate = Query::find_candidate_by_id(db, application)
|
||||||
.await
|
.await?
|
||||||
.map_err(|_| ServiceError::DbError)?
|
|
||||||
.ok_or(ServiceError::CandidateNotFound)?;
|
.ok_or(ServiceError::CandidateNotFound)?;
|
||||||
|
|
||||||
let parent = Query::find_parent_by_id(db, application)
|
let parent = Query::find_parent_by_id(db, application)
|
||||||
.await
|
.await?
|
||||||
.map_err(|_| ServiceError::DbError)?
|
|
||||||
.ok_or(ServiceError::ParentNotFound)?;
|
.ok_or(ServiceError::ParentNotFound)?;
|
||||||
|
|
||||||
let Ok(admin_public_keys) = Query::get_all_admin_public_keys(db).await else {
|
let admin_public_keys = Query::get_all_admin_public_keys(db).await?;
|
||||||
return Err(ServiceError::DbError);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut admin_public_keys_refrence: Vec<&str> =
|
let mut admin_public_keys_refrence: Vec<&str> =
|
||||||
admin_public_keys.iter().map(|s| &**s).collect();
|
admin_public_keys.iter().map(|s| &**s).collect();
|
||||||
|
|
@ -70,9 +66,9 @@ impl ApplicationService {
|
||||||
) -> Result<ApplicationDetails, ServiceError> {
|
) -> Result<ApplicationDetails, ServiceError> {
|
||||||
let candidate = match Query::find_candidate_by_id(db, application_id).await {
|
let candidate = match Query::find_candidate_by_id(db, application_id).await {
|
||||||
Ok(candidate) => candidate.unwrap(),
|
Ok(candidate) => candidate.unwrap(),
|
||||||
Err(_) => return Err(ServiceError::DbError), // TODO: logging
|
Err(e) => return Err(ServiceError::DbError(e)), // TODO: logging
|
||||||
};
|
};
|
||||||
let parent = Query::find_parent_by_id(db, application_id).await.unwrap().unwrap();
|
let parent = Query::find_parent_by_id(db, application_id).await?.unwrap();
|
||||||
|
|
||||||
match crypto::verify_password((&password).to_string(), candidate.code.clone()).await {
|
match crypto::verify_password((&password).to_string(), candidate.code.clone()).await {
|
||||||
Ok(valid) => {
|
Ok(valid) => {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
use entity::{candidate};
|
use entity::candidate;
|
||||||
use sea_orm::{prelude::Uuid, DbConn};
|
use sea_orm::{prelude::Uuid, DbConn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
candidate_details::EncryptedApplicationDetails,
|
||||||
crypto::{self, hash_password},
|
crypto::{self, hash_password},
|
||||||
error::ServiceError,
|
error::ServiceError,
|
||||||
Mutation, Query, candidate_details::{EncryptedApplicationDetails},
|
Mutation, Query,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{session_service::{AdminUser, SessionService}};
|
use super::session_service::{AdminUser, SessionService};
|
||||||
|
|
||||||
const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"];
|
const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"];
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@ impl CandidateService {
|
||||||
return Err(ServiceError::CryptoHashFailed);
|
return Err(ServiceError::CryptoHashFailed);
|
||||||
};
|
};
|
||||||
|
|
||||||
Mutation::create_candidate(
|
let candidate = Mutation::create_candidate(
|
||||||
db,
|
db,
|
||||||
application_id,
|
application_id,
|
||||||
hashed_password,
|
hashed_password,
|
||||||
|
|
@ -61,8 +62,8 @@ impl CandidateService {
|
||||||
pubkey,
|
pubkey,
|
||||||
encrypted_priv_key,
|
encrypted_priv_key,
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|_| ServiceError::DbError)
|
Ok(candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::services) async fn add_candidate_details(
|
pub(in crate::services) async fn add_candidate_details(
|
||||||
|
|
@ -70,22 +71,21 @@ impl CandidateService {
|
||||||
candidate: candidate::Model,
|
candidate: candidate::Model,
|
||||||
enc_details: EncryptedApplicationDetails,
|
enc_details: EncryptedApplicationDetails,
|
||||||
) -> Result<entity::candidate::Model, ServiceError> {
|
) -> Result<entity::candidate::Model, ServiceError> {
|
||||||
Mutation::add_candidate_details(db, candidate, enc_details.clone())
|
let model = Mutation::add_candidate_details(db, candidate, enc_details.clone()).await?;
|
||||||
.await
|
Ok(model)
|
||||||
.map_err(|_| ServiceError::DbError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_set_up(candidate: &candidate::Model) -> bool {
|
pub fn is_set_up(candidate: &candidate::Model) -> bool {
|
||||||
candidate.name.is_some() &&
|
candidate.name.is_some()
|
||||||
candidate.surname.is_some() &&
|
&& candidate.surname.is_some()
|
||||||
candidate.birthplace.is_some() &&
|
&& candidate.birthplace.is_some()
|
||||||
candidate.birthdate.is_some() &&
|
&& candidate.birthdate.is_some()
|
||||||
candidate.address.is_some() &&
|
&& candidate.address.is_some()
|
||||||
candidate.telephone.is_some() &&
|
&& candidate.telephone.is_some()
|
||||||
candidate.citizenship.is_some() &&
|
&& candidate.citizenship.is_some()
|
||||||
candidate.email.is_some() &&
|
&& candidate.email.is_some()
|
||||||
candidate.sex.is_some() &&
|
&& candidate.sex.is_some()
|
||||||
candidate.study.is_some()
|
&& candidate.study.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_cover_letter(candidate_id: i32, letter: Vec<u8>) -> Result<(), ServiceError> {
|
pub async fn add_cover_letter(candidate_id: i32, letter: Vec<u8>) -> Result<(), ServiceError> {
|
||||||
|
|
@ -128,12 +128,12 @@ impl CandidateService {
|
||||||
ip_addr: String,
|
ip_addr: String,
|
||||||
) -> Result<(String, String), ServiceError> {
|
) -> Result<(String, String), ServiceError> {
|
||||||
let candidate = Query::find_candidate_by_id(db, candidate_id)
|
let candidate = Query::find_candidate_by_id(db, candidate_id)
|
||||||
.await
|
.await?
|
||||||
.map_err(|_| ServiceError::DbError)?
|
|
||||||
.ok_or(ServiceError::CandidateNotFound)?;
|
.ok_or(ServiceError::CandidateNotFound)?;
|
||||||
|
|
||||||
let session_id =
|
let session_id =
|
||||||
SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr).await;
|
SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr)
|
||||||
|
.await;
|
||||||
match session_id {
|
match session_id {
|
||||||
Ok(session_id) => {
|
Ok(session_id) => {
|
||||||
let private_key = Self::decrypt_private_key(candidate, password).await?;
|
let private_key = Self::decrypt_private_key(candidate, password).await?;
|
||||||
|
|
@ -147,7 +147,7 @@ impl CandidateService {
|
||||||
match SessionService::auth_user_session(db, session_uuid).await {
|
match SessionService::auth_user_session(db, session_uuid).await {
|
||||||
Ok(user) => match user {
|
Ok(user) => match user {
|
||||||
AdminUser::Candidate(candidate) => Ok(candidate),
|
AdminUser::Candidate(candidate) => Ok(candidate),
|
||||||
AdminUser::Admin(_) => Err(ServiceError::DbError),
|
AdminUser::Admin(_) => unreachable!(),
|
||||||
},
|
},
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
|
|
@ -168,17 +168,14 @@ impl CandidateService {
|
||||||
mod tests {
|
mod tests {
|
||||||
use sea_orm::{Database, DbConn};
|
use sea_orm::{Database, DbConn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{crypto, services::candidate_service::CandidateService, Mutation};
|
||||||
crypto,
|
|
||||||
services::candidate_service::{CandidateService}, Mutation,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::EncryptedApplicationDetails;
|
use super::EncryptedApplicationDetails;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use entity::{parent, candidate};
|
use entity::{candidate, parent};
|
||||||
|
|
||||||
use crate::services::application_service::ApplicationService;
|
|
||||||
use crate::candidate_details::ApplicationDetails;
|
use crate::candidate_details::ApplicationDetails;
|
||||||
|
use crate::services::application_service::ApplicationService;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_application_id_validation() {
|
async fn test_application_id_validation() {
|
||||||
|
|
@ -225,14 +222,12 @@ mod tests {
|
||||||
|
|
||||||
let secret_message = "trnka".to_string();
|
let secret_message = "trnka".to_string();
|
||||||
|
|
||||||
|
|
||||||
let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string())
|
let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string())
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Mutation::create_parent(&db, 103151)
|
Mutation::create_parent(&db, 103151).await.unwrap();
|
||||||
.await.unwrap();
|
|
||||||
|
|
||||||
let encrypted_message =
|
let encrypted_message =
|
||||||
crypto::encrypt_password_with_recipients(&secret_message, &vec![&candidate.public_key])
|
crypto::encrypt_password_with_recipients(&secret_message, &vec![&candidate.public_key])
|
||||||
|
|
@ -255,10 +250,15 @@ mod tests {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) {
|
async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) {
|
||||||
let plain_text_password = "test".to_string();
|
let plain_text_password = "test".to_string();
|
||||||
let (candidate, parent) = ApplicationService::create_candidate_with_parent(&db, 103151, &plain_text_password, "".to_string())
|
let (candidate, parent) = ApplicationService::create_candidate_with_parent(
|
||||||
.await
|
&db,
|
||||||
.ok()
|
103151,
|
||||||
.unwrap();
|
&plain_text_password,
|
||||||
|
"".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let form = ApplicationDetails {
|
let form = ApplicationDetails {
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
|
|
@ -275,7 +275,6 @@ mod tests {
|
||||||
parent_surname: "test".to_string(),
|
parent_surname: "test".to_string(),
|
||||||
parent_telephone: "test".to_string(),
|
parent_telephone: "test".to_string(),
|
||||||
parent_email: "test".to_string(),
|
parent_email: "test".to_string(),
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ApplicationService::add_all_details(&db, candidate.application, form)
|
ApplicationService::add_all_details(&db, candidate.application, form)
|
||||||
|
|
@ -300,7 +299,9 @@ mod tests {
|
||||||
let dec_priv_key = crypto::decrypt_password(enc_candidate.private_key.clone(), password)
|
let dec_priv_key = crypto::decrypt_password(enc_candidate.private_key.clone(), password)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let enc_details = EncryptedApplicationDetails::try_from((enc_candidate, enc_parent)).ok().unwrap();
|
let enc_details = EncryptedApplicationDetails::try_from((enc_candidate, enc_parent))
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
let dec_details = enc_details.decrypt(dec_priv_key).await.ok().unwrap();
|
let dec_details = enc_details.decrypt(dec_priv_key).await.ok().unwrap();
|
||||||
|
|
||||||
assert_eq!(dec_details.name, "test"); // TODO: test every element
|
assert_eq!(dec_details.name, "test"); // TODO: test every element
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ impl ParentService {
|
||||||
application_id: i32,
|
application_id: i32,
|
||||||
) -> Result<parent::Model, ServiceError> {
|
) -> Result<parent::Model, ServiceError> {
|
||||||
let parent = Mutation::create_parent(db, application_id)
|
let parent = Mutation::create_parent(db, application_id)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|_| ServiceError::DbError)?;
|
|
||||||
|
|
||||||
Ok(parent)
|
Ok(parent)
|
||||||
}
|
}
|
||||||
|
|
@ -23,8 +22,7 @@ impl ParentService {
|
||||||
enc_details: EncryptedApplicationDetails,
|
enc_details: EncryptedApplicationDetails,
|
||||||
) -> Result<parent::Model, ServiceError> {
|
) -> Result<parent::Model, ServiceError> {
|
||||||
let parent = Mutation::add_parent_details(db, parent, enc_details)
|
let parent = Mutation::add_parent_details(db, parent, enc_details)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|_| ServiceError::DbError)?;
|
|
||||||
|
|
||||||
Ok(parent)
|
Ok(parent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ impl SessionService {
|
||||||
Some(candidate) => candidate,
|
Some(candidate) => candidate,
|
||||||
None => return Err(ServiceError::CandidateNotFound),
|
None => return Err(ServiceError::CandidateNotFound),
|
||||||
},
|
},
|
||||||
Err(_) => return Err(ServiceError::DbError),
|
Err(e) => return Err(ServiceError::DbError(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// compare passwords
|
// compare passwords
|
||||||
|
|
@ -80,7 +80,7 @@ impl SessionService {
|
||||||
Some(admin) => admin,
|
Some(admin) => admin,
|
||||||
None => return Err(ServiceError::CandidateNotFound),
|
None => return Err(ServiceError::CandidateNotFound),
|
||||||
},
|
},
|
||||||
Err(_) => return Err(ServiceError::DbError),
|
Err(e) => return Err(ServiceError::DbError(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// compare passwords
|
// compare passwords
|
||||||
|
|
@ -102,7 +102,7 @@ impl SessionService {
|
||||||
Ok(session) => session,
|
Ok(session) => session,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error creating session: {}", e);
|
eprintln!("Error creating session: {}", e);
|
||||||
return Err(ServiceError::DbError);
|
return Err(ServiceError::DbError(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ impl SessionService {
|
||||||
Some(session) => session,
|
Some(session) => session,
|
||||||
None => return Err(ServiceError::UserNotFoundBySessionId),
|
None => return Err(ServiceError::UserNotFoundBySessionId),
|
||||||
},
|
},
|
||||||
Err(_) => return Err(ServiceError::DbError),
|
Err(e) => return Err(ServiceError::DbError(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let now = chrono::Utc::now().naive_utc();
|
let now = chrono::Utc::now().naive_utc();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue