diff --git a/api/src/lib.rs b/api/src/lib.rs index 22a905d..0759339 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -70,6 +70,11 @@ async fn start() -> Result<(), rocket::Error> { routes::admin::create_candidate, ], ) + .mount( + "/admin/list", + routes![ + routes::admin::list_candidates, + ]) .register("/", catchers![]) .launch() .await diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index b57261f..c9a819e 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use portfolio_core::{ crypto::random_8_char_string, - services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService}, + services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService}, responses::CandidateResponse, }; use requests::{AdminLoginRequest, RegisterRequest}; use rocket::http::{Cookie, Status, CookieJar}; @@ -86,3 +86,19 @@ pub async fn create_candidate( Ok(plain_text_password) } + +#[get("/candidates?")] +pub async fn list_candidates( + conn: Connection<'_, Db>, + session: AdminAuth, + field: Option, +) -> Result>, Custom> { + let db = conn.into_inner(); + let private_key = session.get_private_key(); + + let candidates = CandidateService::list_candidates(private_key, db, field) + .await + .map_err(|e| Custom(Status::InternalServerError, e.to_string()))?; + + Ok(Json(candidates)) +} diff --git a/core/src/candidate_details.rs b/core/src/candidate_details.rs index dc6350a..2de8f1a 100644 --- a/core/src/candidate_details.rs +++ b/core/src/candidate_details.rs @@ -185,6 +185,33 @@ impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails } } +// TODO: use this for ApplicationResult??? +/* impl TryFrom for EncryptedApplicationDetails { + type Error = ServiceError; + + fn try_from(value: ApplicationResult) -> Result { + Ok(EncryptedApplicationDetails { + name: EncryptedString::try_from(value.name)?, + surname: EncryptedString::try_from(value.surname)?, + birthplace: todo!(), + birthdate: todo!(), + address: todo!(), + telephone: todo!(), + citizenship: todo!(), + email: todo!(), + sex: todo!(), + study: todo!(), + parent_name: todo!(), + parent_surname: todo!(), + parent_telephone: todo!(), + parent_email: todo!(), + + }) + } + + +} */ + #[derive(Debug, Serialize, Deserialize)] pub struct ApplicationDetails { // Candidate diff --git a/core/src/database/query/candidate.rs b/core/src/database/query/candidate.rs index 2a6d7c7..37478b9 100644 --- a/core/src/database/query/candidate.rs +++ b/core/src/database/query/candidate.rs @@ -1,7 +1,23 @@ -use crate::Query; +use crate::{Query}; -use ::entity::{candidate, candidate::Entity as Candidate}; +use ::entity::{candidate, candidate::Entity as Candidate, parent}; use sea_orm::*; +use serde::Serialize; + + +pub const PAGE_SIZE: u64 = 20; + +#[derive(FromQueryResult, Serialize)] +pub struct ApplicationResult { + pub application: i32, + pub name: Option, + pub surname: Option, + pub study: Option, + pub citizenship: Option, + + pub parent_name: Option, + pub parent_surname: Option, +} impl Query { pub async fn find_candidate_by_id( @@ -10,6 +26,21 @@ impl Query { ) -> Result, DbErr> { Candidate::find_by_id(id).one(db).await } + + pub async fn list_candidates( + db: &DbConn, + field_of_study: Option, + ) -> Result, DbErr> { + Candidate::find() + .join(JoinType::InnerJoin, candidate::Relation::Parent.def()) + .column_as(parent::Column::Name, "parent_name") + .column_as(parent::Column::Surname, "parent_surname") + .into_model::() + .paginate(db, PAGE_SIZE) + .fetch() + .await + } + } #[cfg(test)] diff --git a/core/src/lib.rs b/core/src/lib.rs index 322409c..579ddcd 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -5,6 +5,7 @@ pub mod services; pub mod error; pub mod candidate_details; pub mod util; +pub mod responses; pub use database::mutation::*; pub use database::query::*; diff --git a/core/src/responses.rs b/core/src/responses.rs new file mode 100644 index 0000000..c2cbdd2 --- /dev/null +++ b/core/src/responses.rs @@ -0,0 +1,47 @@ +use serde::Serialize; + +use crate::{candidate_details::EncryptedString, error::ServiceError}; + +#[derive(Debug, Serialize)] +pub struct CandidateResponse { + pub application_id: i32, + pub name: String, + pub surname: String, + pub study: String, + pub submitted: bool, +} + +impl CandidateResponse { + pub async fn from_encrypted( + private_key: &String, + application_id: i32, + name_opt: Option, + surname_opt: Option, + study_opt: Option, + submitted: bool, + ) -> Result { + let name = decrypt_if_exists(private_key, name_opt).await?; + let surname = decrypt_if_exists(private_key, surname_opt).await?; + let study = decrypt_if_exists(private_key, study_opt).await?; + Ok( + Self { + application_id, + name, + surname, + study, + submitted, + } + ) + } + +} + +async fn decrypt_if_exists( + private_key: &String, + encrypted_string: Option, +) -> Result { + match EncryptedString::try_from(encrypted_string) { + Ok(encrypted_string) => Ok(encrypted_string.decrypt(private_key).await?), + Err(_) => Ok(String::from("")), + } +} \ No newline at end of file diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 59c2f1e..9deacb1 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -423,6 +423,19 @@ mod tests { assert!(!CandidateService::is_application_id_valid(101)); } + // TODO + /* #[tokio::test] + async fn test_list_candidates() { + let db = get_memory_sqlite_connection().await; + let candidates = CandidateService::list_candidates(&db, None).await.unwrap(); + assert_eq!(candidates.len(), 0); + + put_user_data(&db).await; + + let candidates = CandidateService::list_candidates(&db, None).await.unwrap(); + assert_eq!(candidates.len(), 1); + } */ + #[tokio::test] async fn test_encrypt_decrypt_private_key_with_passphrase() { let db = get_memory_sqlite_connection().await; @@ -479,7 +492,7 @@ mod tests { citizenship: "test".to_string(), email: "test".to_string(), sex: "test".to_string(), - study: "test".to_string(), + study: "KB".to_string(), parent_name: "test".to_string(), parent_surname: "test".to_string(), parent_telephone: "test".to_string(), diff --git a/core/src/services/mod.rs b/core/src/services/mod.rs index 9cea043..28fe987 100644 --- a/core/src/services/mod.rs +++ b/core/src/services/mod.rs @@ -2,5 +2,4 @@ pub mod session_service; pub mod candidate_service; pub mod admin_service; pub mod parent_service; -pub mod application_service; -pub mod portfolio_service; \ No newline at end of file +pub mod application_service; \ No newline at end of file diff --git a/core/src/services/portfolio_service.rs b/core/src/services/portfolio_service.rs deleted file mode 100644 index 3c62219..0000000 --- a/core/src/services/portfolio_service.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::path::Path; - -use tokio::io::AsyncWriteExt; - -use crate::error::ServiceError; - -pub struct PortfolioService; - -impl PortfolioService { - pub async fn write_portfolio_file( - candidate_id: i32, - data: Vec, - filename: &str, - ) -> Result<(), ServiceError> { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); - - let mut file = tokio::fs::File::create(cache_path.join(filename)).await?; - - file.write_all(&data).await?; - - Ok(()) - } - - pub async fn is_portfolio_complete(candidate_id: i32) -> bool { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); - - tokio::fs::metadata(cache_path.join("MOTIVACNI_DOPIS.pdf")).await.is_ok() - && tokio::fs::metadata(cache_path.join("PORTFOLIO.pdf")).await.is_ok() - && tokio::fs::metadata(cache_path.join("PORTFOLIO.zip")).await.is_ok() - } -} \ No newline at end of file