Skip to content

Commit

Permalink
Merge pull request #218 from admisio/candidate_csv
Browse files Browse the repository at this point in the history
Candidate CSV export with linked fields of study
  • Loading branch information
EETagent committed May 29, 2023
2 parents db72eb2 + f75a04b commit d627fe3
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 31 deletions.
1 change: 1 addition & 0 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ pub fn rocket() -> Rocket<Build> {
routes![
routes::admin::list_candidates,
routes::admin::list_candidates_csv,
routes::admin::list_admissions_csv
]
)
.register("/", catchers![])
Expand Down
22 changes: 20 additions & 2 deletions api/src/routes/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ use std::net::{SocketAddr, IpAddr, Ipv4Addr};

use portfolio_core::{
crypto::random_12_char_string,
services::{admin_service::AdminService, application_service::ApplicationService, portfolio_service::PortfolioService}, models::{candidate::{CreateCandidateResponse, ApplicationDetails}, auth::AuthenticableTrait, application::ApplicationResponse}, sea_orm::prelude::Uuid, Query, error::ServiceError, utils::csv,
services::{admin_service::AdminService, application_service::ApplicationService, portfolio_service::PortfolioService}, models::{candidate::{CreateCandidateResponse, ApplicationDetails}, auth::AuthenticableTrait, application::ApplicationResponse}, sea_orm::prelude::Uuid, Query, error::ServiceError,
};
use requests::{AdminLoginRequest, RegisterRequest};
use rocket::http::{Cookie, Status, CookieJar};
use rocket::response::status::Custom;
use rocket::serde::json::Json;

use sea_orm_rocket::Connection;
use portfolio_core::utils::csv::{ApplicationCsv, CandidateCsv, CsvExporter};

use crate::{guards::request::{auth::AdminAuth}, pool::Db, requests};

Expand Down Expand Up @@ -152,7 +153,24 @@ pub async fn list_candidates_csv(
let db = conn.into_inner();
let private_key = session.get_private_key();

let candidates = csv::export(db, private_key)
let candidates = ApplicationCsv::export(db, private_key)
.await
.map_err(to_custom_error)?;

Ok(
candidates
)
}

#[get("/admissions_csv")]
pub async fn list_admissions_csv(
conn: Connection<'_, Db>,
session: AdminAuth,
) -> Result<Vec<u8>, Custom<String>> {
let db = conn.into_inner();
let private_key = session.get_private_key();

let candidates = CandidateCsv::export(db, private_key)
.await
.map_err(to_custom_error)?;

Expand Down
5 changes: 3 additions & 2 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use url::Url;

use portfolio_core::{crypto, Query};
use portfolio_core::services::portfolio_service::{FileType};
use portfolio_core::utils::csv::{ApplicationCsv, CsvExporter};

async fn get_admin_private_key(db: &DbConn, sub_matches: &ArgMatches) -> Result<String, Box<dyn std::error::Error>> {
Ok(match (sub_matches.get_one::<String>("key"), sub_matches.get_one::<String>("password")) {
Expand Down Expand Up @@ -253,7 +254,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let key = get_admin_private_key(&db, sub_matches).await?;

let output = sub_matches.get_one::<PathBuf>("output").unwrap();
let csv = portfolio_core::utils::csv::export(&db, key).await?;
let csv = ApplicationCsv::export(&db, key).await?;
tokio::fs::write(output, csv).await?;
},
Some(("portfolio", sub_matches)) => {
Expand All @@ -276,7 +277,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let output = sub_matches.get_one::<PathBuf>("output").unwrap();
tokio::fs::create_dir_all(&output).await?;

let csv = portfolio_core::utils::csv::export(&db, key.to_string()).await?;
let csv = ApplicationCsv::export(&db, key.to_string()).await?;
tokio::fs::write(output.join("personal_data.csv"), csv).await?;
println!("Exported personal data to personal_data.csv");

Expand Down
10 changes: 9 additions & 1 deletion core/src/database/query/parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use entity::candidate;
use entity::parent;
use entity::parent::Model;
use sea_orm::ModelTrait;
use sea_orm::{EntityTrait, ModelTrait};
use sea_orm::{DbConn, DbErr};

use crate::Query;
Expand All @@ -17,6 +17,14 @@ impl Query {
.all(db)
.await
}

pub async fn list_all_parents(
db: &DbConn,
) -> Result<Vec<Model>, DbErr> {
parent::Entity::find()
.all(db)
.await
}
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions core/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use log::error;
use thiserror::Error;

#[derive(Error, Debug)]
Expand Down Expand Up @@ -80,6 +81,8 @@ pub enum ServiceError {
CsvIntoInnerError,
#[error("Format error")]
FormatError,
#[error("Invalid field of study")]
InvalidFieldOfStudy,
}

impl ServiceError {
Expand Down Expand Up @@ -125,6 +128,7 @@ impl ServiceError {
ServiceError::CsvError(_) => 500,
ServiceError::CsvIntoInnerError => 500,
ServiceError::FormatError => 500,
ServiceError::InvalidFieldOfStudy => 500,
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/models/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub struct ApplicationRow {
pub sex: Option<String>,
#[serde(rename = "Rodné číslo")]
pub personal_identification_number: Option<String>,
#[serde(rename = "Název školy")]
#[serde(rename = "Název školy (IZO)")]
pub school_name: Option<String>,
#[serde(rename = "Zdravotní pojištění")]
pub health_insurance: Option<String>,
Expand Down
111 changes: 111 additions & 0 deletions core/src/models/candidate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use chrono::NaiveDate;
use sea_orm::strum::Display;
use entity::{application, candidate};
use serde::{Deserialize, Serialize};
use validator::Validate;
Expand All @@ -9,6 +10,7 @@ use crate::{

use super::{candidate_details::{EncryptedString, EncryptedCandidateDetails}, grade::GradeList, school::School};

#[derive(Debug, Clone, Serialize, Display)]
pub enum FieldOfStudy {
G,
IT,
Expand Down Expand Up @@ -36,6 +38,28 @@ impl From<i32> for FieldOfStudy {
}
}

impl TryFrom<String> for FieldOfStudy {
type Error = ServiceError;
fn try_from(s: String) -> Result<Self, ServiceError> {
match s.as_str() {
"7941K41-Gymnázium" => Ok(FieldOfStudy::G),
"1820M01-Informační technologie" => Ok(FieldOfStudy::IT), // TODO: constants
"1820M01-Informační technologie - Kybernetická bezpečnost" => Ok(FieldOfStudy::KB),
_ => Err(ServiceError::InvalidFieldOfStudy),
}
}
}

impl Into<i32> for FieldOfStudy {
fn into(self) -> i32 {
match self {
FieldOfStudy::G => 101,
FieldOfStudy::IT => 102,
FieldOfStudy::KB => 103,
}
}
}

/// Minimal candidate response containing database only not null fields
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -144,4 +168,91 @@ impl NewCandidateResponse {
field_of_study,
})
}
}

#[derive(Debug, Serialize, PartialEq)]
pub enum FieldsCombination {
#[serde(rename = "Žádný obor na SSPŠ")]
Unknown,
#[serde(rename = "G")]
G,
#[serde(rename = "IT")]
IT,
#[serde(rename = "KB")]
KB,
#[serde(rename = "G a IT")]
GIt,
#[serde(rename = "G a KB")]
GKb,
#[serde(rename = "IT a KB")]
ItKb,
}

impl FieldsCombination {
pub fn from_fields(first: &Option<FieldOfStudy>, second: &Option<FieldOfStudy>) -> Self {
match (first, second) {
(None, None) => FieldsCombination::Unknown,
(Some(FieldOfStudy::G), None) => FieldsCombination::G,
(Some(FieldOfStudy::IT), None) => FieldsCombination::IT,
(Some(FieldOfStudy::KB), None) => FieldsCombination::KB,
(None, Some(FieldOfStudy::G)) => FieldsCombination::G,
(None, Some(FieldOfStudy::IT)) => FieldsCombination::IT,
(None, Some(FieldOfStudy::KB)) => FieldsCombination::KB,
// Field combinations
(Some(FieldOfStudy::G), Some(FieldOfStudy::IT)) => FieldsCombination::GIt,
(Some(FieldOfStudy::G), Some(FieldOfStudy::KB)) => FieldsCombination::GKb,
(Some(FieldOfStudy::IT), Some(FieldOfStudy::KB)) => FieldsCombination::ItKb,
(Some(FieldOfStudy::IT), Some(FieldOfStudy::G)) => FieldsCombination::GIt,
(Some(FieldOfStudy::KB), Some(FieldOfStudy::G)) => FieldsCombination::GKb,
(Some(FieldOfStudy::KB), Some(FieldOfStudy::IT)) => FieldsCombination::ItKb,
// Some candidates filled in the same field twice
(Some(FieldOfStudy::G), Some(FieldOfStudy::G)) => FieldsCombination::G,
(Some(FieldOfStudy::IT), Some(FieldOfStudy::IT)) => FieldsCombination::IT,
(Some(FieldOfStudy::KB), Some(FieldOfStudy::KB)) => FieldsCombination::KB,
}
}
}

#[derive(Debug, Serialize)]
pub struct CandidateRow {
#[serde(rename = "Číslo uchazeče (přiděleno systémem)")]
pub id: i32,
#[serde(rename = "Ev. č. první přihlášky")]
pub first_application: i32,
#[serde(rename = "Ev. č. druhé přihlášky (pokud podával dvě)")]
pub second_application: Option<i32>,
#[serde(rename = "Rodné číslo")]
pub personal_id_number: String,
#[serde(rename = "Bude dělat JPZ na SSPŠ 13. 4.")]
pub first_day_admissions: bool,
#[serde(rename = "Bude dělat JPZ na SSPŠ 14. 4.")]
pub second_day_admissions: bool,
#[serde(rename = "Obor první přihlášky SSPŠ 13. 4.")]
pub first_day_field: Option<FieldOfStudy>,
#[serde(rename = "Obor druhé přihlášky SSPŠ 14. 4.")]
pub second_day_field: Option<FieldOfStudy>,
#[serde(rename = "Kombinace SSPŠ oborů")]
pub fields_combination: FieldsCombination,
#[serde(rename = "Název první školy (JPZ 13. 4.)")]
pub first_school: String,
#[serde(rename = "Obor první školy")]
pub first_school_field: String,
#[serde(rename = "Název druhé školy (JPZ 14. 4.)")]
pub second_school: String,
#[serde(rename = "Obor druhé školy")]
pub second_school_field: String,
#[serde(rename = "Obory vyplněné uchazečem odpovídají s přihláškami")]
pub fields_match: bool,
#[serde(rename = "Jméno (pokud vyplnil)")]
pub name: String,
#[serde(rename = "Příjmení (pokud vyplnil)")]
pub surname: String,
#[serde(rename = "Email uchazeče (pokud vyplnil)")]
pub email: String,
#[serde(rename = "Telefon uchazeče (pokud vyplnil)")]
pub telephone: String,
#[serde(rename = "Email zákonného zástupce (pokud vyplnil)")]
pub parent_email: Option<String>,
#[serde(rename = "Telefon zákonného zástupce (pokud vyplnil)")]
pub parent_telephone: Option<String>,
}
Loading

0 comments on commit d627fe3

Please sign in to comment.