feat!: candidate list field of study filtering, pagination

This commit is contained in:
Sebastian Pravda 2023-01-15 20:42:32 +01:00
parent 51d86ff507
commit 7a9910c9d7
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
10 changed files with 67 additions and 17 deletions

View file

@ -123,10 +123,9 @@ pub async fn list_candidates(
if !(field == "KB".to_string() || field == "IT".to_string() || field == "G") { if !(field == "KB".to_string() || field == "IT".to_string() || field == "G") {
return Err(Custom(Status::BadRequest, "Invalid field of study".to_string())); return Err(Custom(Status::BadRequest, "Invalid field of study".to_string()));
} }
} }
let candidates = ApplicationService::list_applications(&private_key, db) let candidates = ApplicationService::list_applications(&private_key, db, field, page)
.await.map_err(to_custom_error)?; .await.map_err(to_custom_error)?;
Ok( Ok(

View file

@ -2,7 +2,7 @@ use ::entity::application;
use log::{info, warn}; use log::{info, warn};
use sea_orm::{DbConn, DbErr, Set, ActiveModelTrait, IntoActiveModel, DeleteResult, ModelTrait}; use sea_orm::{DbConn, DbErr, Set, ActiveModelTrait, IntoActiveModel, DeleteResult, ModelTrait};
use crate::Mutation; use crate::{Mutation, models::candidate::FieldOfStudy};
impl Mutation { impl Mutation {
pub async fn create_application( pub async fn create_application(
@ -14,8 +14,10 @@ impl Mutation {
pubkey: String, pubkey: String,
encrypted_priv_key: String, encrypted_priv_key: String,
) -> Result<application::Model, DbErr> { ) -> Result<application::Model, DbErr> {
let field_of_study = FieldOfStudy::from(application_id);
let insert = application::ActiveModel { let insert = application::ActiveModel {
id: Set(application_id), id: Set(application_id),
field_of_study: Set(field_of_study.into()),
personal_id_number: Set(enc_personal_id_number), personal_id_number: Set(enc_personal_id_number),
password: Set(hashed_password), password: Set(hashed_password),
candidate_id: Set(candidate_id), candidate_id: Set(candidate_id),
@ -23,7 +25,6 @@ impl Mutation {
private_key: Set(encrypted_priv_key), private_key: Set(encrypted_priv_key),
created_at: Set(chrono::offset::Local::now().naive_local()), created_at: Set(chrono::offset::Local::now().naive_local()),
updated_at: Set(chrono::offset::Local::now().naive_local()), updated_at: Set(chrono::offset::Local::now().naive_local()),
..Default::default()
} }
.insert(db) .insert(db)
.await?; .await?;

View file

@ -1,5 +1,7 @@
use entity::{application, candidate}; use entity::{application, candidate};
use sea_orm::{EntityTrait, DbErr, DbConn, ModelTrait, FromQueryResult, QuerySelect, JoinType, RelationTrait, QueryFilter, ColumnTrait}; use sea_orm::{EntityTrait, DbErr, DbConn, ModelTrait, FromQueryResult, QuerySelect, JoinType, RelationTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait};
const PAGE_SIZE: u64 = 20;
#[derive(FromQueryResult, Clone)] #[derive(FromQueryResult, Clone)]
pub struct ApplicationCandidateJoin { pub struct ApplicationCandidateJoin {
@ -12,7 +14,7 @@ pub struct ApplicationCandidateJoin {
pub telephone: Option<String>, pub telephone: Option<String>,
} }
use crate::Query; use crate::{Query};
impl Query { impl Query {
pub async fn find_application_by_id( pub async fn find_application_by_id(
@ -36,9 +38,16 @@ impl Query {
pub async fn list_applications( pub async fn list_applications(
db: &DbConn, db: &DbConn,
field_of_study: Option<String>,
page: Option<u64>,
) -> Result<Vec<ApplicationCandidateJoin>, DbErr> { ) -> Result<Vec<ApplicationCandidateJoin>, DbErr> {
application::Entity::find() let select = application::Entity::find();
// .column_as(application::Column::Id, "application_id") let query = if let Some(field) = field_of_study {
select.filter(application::Column::FieldOfStudy.eq(field))
} else {
select
}
.order_by(application::Column::Id, sea_orm::Order::Asc)
.join(JoinType::InnerJoin, application::Relation::Candidate.def()) .join(JoinType::InnerJoin, application::Relation::Candidate.def())
.column_as(application::Column::Id, "application_id") .column_as(application::Column::Id, "application_id")
.column_as(candidate::Column::Id, "candidate_id") .column_as(candidate::Column::Id, "candidate_id")
@ -46,9 +55,16 @@ impl Query {
.column_as(candidate::Column::Surname, "surname") .column_as(candidate::Column::Surname, "surname")
.column_as(candidate::Column::Email, "email") .column_as(candidate::Column::Email, "email")
.column_as(candidate::Column::Telephone, "telephone") .column_as(candidate::Column::Telephone, "telephone")
.into_model::<ApplicationCandidateJoin>() .into_model::<ApplicationCandidateJoin>();
.all(db)
.await if let Some(page) = page {
query
.paginate(db, PAGE_SIZE)
.fetch_page(page).await
} else {
query
.all(db).await
}
} }
pub async fn list_applications_compact( pub async fn list_applications_compact(

View file

@ -8,6 +8,33 @@ use crate::{
use super::candidate_details::{EncryptedString, EncryptedCandidateDetails}; use super::candidate_details::{EncryptedString, EncryptedCandidateDetails};
pub enum FieldOfStudy {
G,
IT,
KB,
}
impl Into<String> for FieldOfStudy {
fn into(self) -> String {
match self {
FieldOfStudy::G => "G".to_string(),
FieldOfStudy::IT => "IT".to_string(),
FieldOfStudy::KB => "KB".to_string(),
}
}
}
impl From<i32> for FieldOfStudy {
fn from(id: i32) -> Self {
match &id.to_string().as_str()[0..3] {
"101" => FieldOfStudy::G,
"102" => FieldOfStudy::IT,
"103" => FieldOfStudy::KB,
_ => panic!("Invalid field of study id"), // TODO: handle using TryFrom
}
}
}
/// Minimal candidate response containing database only not null fields /// Minimal candidate response containing database only not null fields
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View file

@ -100,11 +100,11 @@ impl ApplicationService {
.filter(|(_, id)| id == &personal_id_number) .filter(|(_, id)| id == &personal_id_number)
.collect(); .collect();
if found_ids.iter().any(|(_, personal_id)| personal_id == &personal_id_number) { if let Some((candidate_id, _)) = found_ids.first() {
Ok( Ok(
Self::find_linkable_candidate(db, Self::find_linkable_candidate(db,
application_id, application_id,
found_ids[0].0, *candidate_id,
personal_id_number personal_id_number
).await? ).await?
) )
@ -230,8 +230,11 @@ impl ApplicationService {
pub async fn list_applications( pub async fn list_applications(
private_key: &String, private_key: &String,
db: &DbConn, db: &DbConn,
field_of_study: Option<String>,
page: Option<u64>,
) -> Result<Vec<ApplicationResponse>, ServiceError> { ) -> Result<Vec<ApplicationResponse>, ServiceError> {
let applications = Query::list_applications(db).await?; let applications = Query::list_applications(db, field_of_study, page).await?;
futures::future::try_join_all( futures::future::try_join_all(
applications applications

View file

@ -79,12 +79,12 @@ pub mod tests {
let db = get_memory_sqlite_connection().await; let db = get_memory_sqlite_connection().await;
let admin = create_admin(&db).await; let admin = create_admin(&db).await;
let private_key = crypto::decrypt_password(admin.private_key, "admin".to_string()).await.unwrap(); let private_key = crypto::decrypt_password(admin.private_key, "admin".to_string()).await.unwrap();
let candidates = ApplicationService::list_applications(&private_key, &db).await.unwrap(); let candidates = ApplicationService::list_applications(&private_key, &db, None, None).await.unwrap();
assert_eq!(candidates.len(), 0); assert_eq!(candidates.len(), 0);
put_user_data(&db).await; put_user_data(&db).await;
let candidates = ApplicationService::list_applications(&private_key, &db).await.unwrap(); let candidates = ApplicationService::list_applications(&private_key, &db, None, None).await.unwrap();
assert_eq!(candidates.len(), 1); assert_eq!(candidates.len(), 1);
} }

View file

View file

@ -2,3 +2,4 @@ pub mod csv;
pub mod filetype; pub mod filetype;
pub mod db; pub mod db;
pub mod date; pub mod date;
pub mod field_of_study;

View file

@ -8,6 +8,7 @@ pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
pub id: i32, pub id: i32,
pub candidate_id: i32, pub candidate_id: i32,
pub field_of_study: String,
pub password: String, pub password: String,
pub public_key: String, pub public_key: String,
pub private_key: String, pub private_key: String,

View file

@ -17,6 +17,7 @@ impl MigrationTrait for Migration {
.not_null() .not_null()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(Application::FieldOfStudy).string().not_null())
.col(ColumnDef::new(Application::CandidateId).integer().not_null()) .col(ColumnDef::new(Application::CandidateId).integer().not_null())
.col(ColumnDef::new(Application::Password).string().not_null()) .col(ColumnDef::new(Application::Password).string().not_null())
.col(ColumnDef::new(Application::PublicKey).string().not_null()) .col(ColumnDef::new(Application::PublicKey).string().not_null())
@ -51,6 +52,7 @@ impl MigrationTrait for Migration {
pub enum Application { pub enum Application {
Table, Table,
Id, Id,
FieldOfStudy,
Password, Password,
PersonalIdNumber, PersonalIdNumber,
PublicKey, PublicKey,