diff --git a/core/src/error.rs b/core/src/error.rs index 22685ec..6640033 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -78,6 +78,8 @@ pub enum ServiceError { CsvError(#[from] csv::Error), #[error("Csv into inner error")] CsvIntoInnerError, + #[error("Format error")] + FormatError, } impl ServiceError { @@ -122,6 +124,7 @@ impl ServiceError { ServiceError::ZipError(_) => 500, ServiceError::CsvError(_) => 500, ServiceError::CsvIntoInnerError => 500, + ServiceError::FormatError => 500, } } diff --git a/core/src/models/application.rs b/core/src/models/application.rs index 6ffe645..a495437 100644 --- a/core/src/models/application.rs +++ b/core/src/models/application.rs @@ -42,40 +42,71 @@ impl ApplicationResponse { /// CSV export (admin endpoint) #[derive(Serialize, Default)] -#[serde(rename_all = "camelCase")] pub struct ApplicationRow { + #[serde(rename = "Ev. č. přihlášky")] pub application: i32, + #[serde(rename = "Jméno")] pub name: Option, + #[serde(rename = "Příjmení")] pub surname: Option, + #[serde(rename = "Rodné příjmení (pokud odlišné)")] pub birth_surname: Option, + #[serde(rename = "Místo narození")] pub birthplace: Option, + #[serde(rename = "Datum narození")] pub birthdate: Option, + #[serde(rename = "Adresa trvalého pobytu")] pub address: Option, + #[serde(rename = "Adresa pro doručování písemností (pokud odlišné)")] pub letter_address: Option, + #[serde(rename = "Telefon")] pub telephone: Option, + #[serde(rename = "Státní občanství")] pub citizenship: Option, + #[serde(rename = "Email")] pub email: Option, + #[serde(rename = "Pohlaví")] pub sex: Option, + #[serde(rename = "Rodné číslo")] pub personal_identification_number: Option, + #[serde(rename = "Název školy")] pub school_name: Option, + #[serde(rename = "Zdravotní pojištění")] pub health_insurance: Option, + #[serde(rename = "Vysvědčení 1/8")] pub diploma_1_8: String, + #[serde(rename = "Vysvědčení 2/8")] pub diploma_2_8: String, + #[serde(rename = "Vysvědčení 1/9")] pub diploma_1_9: String, + #[serde(rename = "Vysvědčení 2/9")] + pub diploma_2_9: String, + #[serde(rename = "První škola - název")] pub first_school_name: Option, + #[serde(rename = "První škola - obor")] pub first_school_field: Option, + #[serde(rename = "Druhá škola - název")] pub second_school_name: Option, + #[serde(rename = "Druhá škola - obor")] pub second_school_field: Option, + #[serde(rename = "Jméno zákonného zástupce")] pub parent_name: Option, + #[serde(rename = "Příjmení zákonného zástupce")] pub parent_surname: Option, + #[serde(rename = "Telefon zákonného zástupce")] pub parent_telephone: Option, + #[serde(rename = "Email zákonného zástupce")] pub parent_email: Option, + #[serde(rename = "Jméno druhého zákonného zástupce")] pub second_parent_name: Option, + #[serde(rename = "Příjmení druhého zákonného zástupce")] pub second_parent_surname: Option, + #[serde(rename = "Telefon druhého zákonného zástupce")] pub second_parent_telephone: Option, + #[serde(rename = "Email druhého zákonného zástupce")] pub second_parent_email: Option, } \ No newline at end of file diff --git a/core/src/models/candidate.rs b/core/src/models/candidate.rs index 7229137..f4807eb 100644 --- a/core/src/models/candidate.rs +++ b/core/src/models/candidate.rs @@ -98,6 +98,7 @@ impl CandidateDetails { pub fn validate_self(&self) -> Result<(), ServiceError> { self.first_school.validate()?; self.second_school.validate()?; + self.grades.validate_self()?; self.validate() .map_err(ServiceError::ValidationError) } diff --git a/core/src/models/grade.rs b/core/src/models/grade.rs index acd72b7..d0177c7 100644 --- a/core/src/models/grade.rs +++ b/core/src/models/grade.rs @@ -1,37 +1,70 @@ use serde::{Serialize, Deserialize}; +use validator::{Validate, ValidationError}; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +use crate::error::ServiceError; + + +fn validate_semester(semester: &str) -> Result<(), ValidationError> { + match semester { + "1/8" | "2/8" | "1/9" | "2/9" => Ok(()), + _ => Err(ValidationError::new("Invalid semester")) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Validate, PartialEq, Eq)] pub struct Grade { + #[validate(length(min = 1, max = 255))] subject: String, + #[validate(length(min = 1, max = 255), custom = "validate_semester")] semester: String, + #[validate(range(min = 1, max = 5))] value: i32, } + +impl Grade { + pub fn validate_self(&self) -> Result<(), ServiceError> { + self.validate() + .map_err(ServiceError::ValidationError) + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct GradeList(Vec); impl GradeList { + pub fn validate_self(&self) -> Result<(), ServiceError> { + self.0.iter() + .map(|grade| grade.validate_self()) + .collect::, _>>() + .map(|_| ()) + } + pub fn from_opt_str(grades: Option) -> Option { grades.map( |grades| serde_json::from_str(&grades).unwrap() // TODO: handle error ) } - pub fn group_by_semester(&self) -> (GradeList, GradeList, GradeList) { + pub fn group_by_semester(&self) -> Result<(GradeList, GradeList, GradeList, GradeList), ServiceError> { let mut first_semester = GradeList::default(); let mut second_semester = GradeList::default(); let mut third_semester = GradeList::default(); + let mut fourth_semester = GradeList::default(); for grade in &self.0 { match grade.semester.as_str() { "1/8" => first_semester.0.push(grade.clone()), "2/8" => second_semester.0.push(grade.clone()), "1/9" => third_semester.0.push(grade.clone()), - _ => panic!("Invalid semester"), + "2/9" => fourth_semester.0.push(grade.clone()), + _ => return Err(ServiceError::FormatError), } } - (first_semester, second_semester, third_semester) + Ok( + (first_semester, second_semester, third_semester, fourth_semester) + ) } } diff --git a/core/src/utils/csv.rs b/core/src/utils/csv.rs index ff4d740..3b0426c 100644 --- a/core/src/utils/csv.rs +++ b/core/src/utils/csv.rs @@ -6,11 +6,16 @@ use crate::{ }; use sea_orm::DbConn; -impl From<(i32, ApplicationDetails)> for ApplicationRow { - fn from((application, d): (i32, ApplicationDetails)) -> Self { +impl TryFrom<(i32, ApplicationDetails)> for ApplicationRow { + type Error = ServiceError; + fn try_from((application, d): (i32, ApplicationDetails)) -> Result { let c = d.candidate; - let (diploma_1_8, diploma_2_8, diploma_1_9) = c.grades.group_by_semester(); - Self { + let (diploma_1_8, + diploma_2_8, + diploma_1_9, + diploma_2_9 + ) = c.grades.group_by_semester()?; + Ok(Self { application, name: Some(c.name), surname: Some(c.surname), @@ -30,6 +35,7 @@ impl From<(i32, ApplicationDetails)> for ApplicationRow { diploma_1_8: diploma_1_8.to_string(), diploma_2_8: diploma_2_8.to_string(), diploma_1_9: diploma_1_9.to_string(), + diploma_2_9: diploma_2_9.to_string(), first_school_name: Some(c.first_school.name().to_owned()), first_school_field: Some(c.first_school.field().to_owned()), @@ -45,7 +51,7 @@ impl From<(i32, ApplicationDetails)> for ApplicationRow { second_parent_surname: d.parents.get(1).map(|p| p.surname.clone()), second_parent_telephone: d.parents.get(1).map(|p| p.telephone.clone()), second_parent_email: d.parents.get(1).map(|p| p.email.clone()), - } + }) } } @@ -59,11 +65,15 @@ pub async fn export(db: &DbConn, private_key: String) -> Result, Service let row: ApplicationRow = match EncryptedApplicationDetails::try_from((&candidate, &parents)) { - Ok(d) => ApplicationRow::from( + Ok(d) => ApplicationRow::try_from( d.decrypt(private_key.to_string()) .await .map(|d| (application.id, d))?, - ), + ) + .unwrap_or(ApplicationRow { + application: application.id, + ..Default::default() + }), Err(_) => ApplicationRow { application: application.id,