[BE] Remove panics from DB code

master
Araozu 2023-12-06 11:39:31 -05:00
parent 4db7c19409
commit 64007d39bd
11 changed files with 234 additions and 90 deletions

View File

@ -6,11 +6,18 @@ use rocket::serde::json::Json;
use crate::json_result::JsonResult; use crate::json_result::JsonResult;
use crate::model::person::{PersonCreate, PersonLink}; use crate::model::person::{PersonCreate, PersonLink};
use crate::model::reniec_person::ReniecPerson; use crate::model::reniec_person::ReniecPerson;
use crate::model::DBError;
use crate::{db, model::person::Person}; use crate::{db, model::person::Person};
#[get("/person/<dni>")] #[get("/person/<dni>")]
pub async fn get_by_dni(dni: i32) -> (Status, Json<JsonResult<Person>>) { pub async fn get_by_dni(dni: i32) -> (Status, Json<JsonResult<Person>>) {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => {
return (Status::InternalServerError, JsonResult::err(reason));
}
};
info!("get person with dni {}", dni); info!("get person with dni {}", dni);
/* /*
@ -23,15 +30,22 @@ pub async fn get_by_dni(dni: i32) -> (Status, Json<JsonResult<Person>>) {
Err(error) => { Err(error) => {
match error { match error {
// Only if the person is not found in DB, continue // Only if the person is not found in DB, continue
sqlx::Error::RowNotFound => (), DBError::Sqlx(sqlx::Error::RowNotFound) => (),
// Otherwise, throw an error // Otherwise, throw an error
_ => { DBError::Sqlx(error) => {
error!("Error searching person with dni {}: {:?}", dni, error); error!("Error searching person with dni {}: {:?}", dni, error);
return ( return (
Status::InternalServerError, Status::InternalServerError,
JsonResult::err(format!("Error buscando persona en DB.")), JsonResult::err(format!("Error buscando persona en DB.")),
); );
} }
DBError::Str(reason) => {
error!("Error searching person with dni {}: {}", dni, reason);
return (
Status::InternalServerError,
JsonResult::err(format!("Error buscando persona en DB.")),
);
}
} }
} }
}; };

View File

@ -224,7 +224,7 @@ fn get_image_info(path: PathBuf) -> ScanInfo {
ScanInfo::Error("Error renombrando archivo.".into()) ScanInfo::Error("Error renombrando archivo.".into())
} }
} };
} }
}; };

View File

@ -1,6 +1,7 @@
#![feature(lazy_cell)]
use cors::Cors; use cors::Cors;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use rocket::tokio;
use sqlx::mysql::MySqlPoolOptions; use sqlx::mysql::MySqlPoolOptions;
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use std::env; use std::env;
@ -18,41 +19,54 @@ pub mod json_result;
static DB: OnceCell<Pool<MySql>> = OnceCell::new(); static DB: OnceCell<Pool<MySql>> = OnceCell::new();
/// Returns a global reference to the database pool /// Returns a global reference to the database pool
/// ///
/// If the database pool has not been initialized, this function will attempt to initialize it /// If the database pool has not been initialized, this function will attempt to initialize it
/// up to 3 times /// up to 3 times
/// ///
/// If the database pool fails to initialize, this function will panic. /// If the database pool fails to initialize, this function will panic.
pub async fn db() -> &'static Pool<MySql> { pub async fn db() -> Result<&'static Pool<MySql>, String> {
let attempts = 3; let attempts = 3;
for _ in 0..attempts { for _ in 0..attempts {
match DB.get() { match DB.get() {
Some(db) => return db, Some(db) => return Ok(db),
None => { None => {
log::info!("DB not initialized, initializing from db()"); log::info!("DB not initialized, initializing from db()");
init_db().await; let _ = init_db().await;
} }
} }
}; }
log::error!("Failed to initialize DB after {} attempts", attempts); log::error!("Failed to initialize DB after {} attempts", attempts);
panic!("DB not initialized"); Err("Failed to initialize DB".to_string())
} }
pub async fn init_db() { pub async fn init_db() -> Result<(), String> {
/* /*
Init DB and set it as a global variable Init DB and set it as a global variable
*/ */
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let db_url = match env::var("DATABASE_URL") {
Ok(url) => url,
Err(_) => return Err("env DATABASE_URL not found".to_string()),
};
let pool = MySqlPoolOptions::new() let pool = MySqlPoolOptions::new()
.max_connections(5) .max_connections(5)
.connect(db_url.as_str()) .connect(db_url.as_str())
.await; .await;
match pool { match pool {
Ok(pool) => DB.set(pool).expect("Failed to set DB pool"), Ok(pool) => match DB.set(pool) {
Err(e) => log::error!("Error connecting to DB: {}", e), Ok(_) => Ok(()),
Err(_) => {
log::error!("Failed to set DB global variable");
Err("Failed to set DB".to_string())
}
},
Err(e) => {
log::error!("Error connecting to DB: {}", e);
Err("Error connecting to DB".to_string())
}
} }
} }

View File

@ -27,12 +27,25 @@ pub struct Course {
} }
impl Course { impl Course {
pub async fn get_all() -> Result<Vec<Course>, sqlx::Error> { pub async fn get_all() -> Result<Vec<Course>, String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => {
return Err(reason);
}
};
let results = sqlx::query!("SELECT * FROM course") let results = sqlx::query!("SELECT * FROM course").fetch_all(db).await;
.fetch_all(db)
.await? let results = match results {
Ok(res) => res,
Err(error) => {
error!("Error getting courses: {:?}", error);
return Err(format!("Error getting courses"));
}
};
let results = results
.iter() .iter()
.map(|d| Course { .map(|d| Course {
course_id: d.course_id, course_id: d.course_id,
@ -46,8 +59,13 @@ impl Course {
Ok(results) Ok(results)
} }
pub async fn get_course_name(course_id: i32) -> Option<String> { pub async fn get_course_name(course_id: i32) -> Result<String, String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => {
return Err(reason);
}
};
let res = sqlx::query!( let res = sqlx::query!(
"SELECT course_name FROM course WHERE course_id = ?", "SELECT course_name FROM course WHERE course_id = ?",
@ -57,8 +75,12 @@ impl Course {
.await; .await;
match res { match res {
Ok(data) => Some(data.course_name), Ok(data) => Ok(data.course_name),
Err(_) => None, Err(sqlx::Error::RowNotFound) => Ok("".into()),
Err(err) => {
log::error!("Error fetching course name: {:?}", err);
return Err("Error fetching course name".into());
}
} }
} }
} }

View File

@ -8,8 +8,11 @@ pub struct CustomLabel {
} }
impl CustomLabel { impl CustomLabel {
pub async fn get_all() -> Result<Vec<CustomLabel>, sqlx::Error> { pub async fn get_all() -> Result<Vec<CustomLabel>, String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => return Err(reason),
};
let result = sqlx::query_as::<_, CustomLabel>( let result = sqlx::query_as::<_, CustomLabel>(
r#" r#"
@ -18,20 +21,37 @@ impl CustomLabel {
"#, "#,
) )
.fetch_all(db) .fetch_all(db)
.await?; .await;
Ok(result) match result {
Ok(res) => Ok(res),
Err(err) => {
log::error!("Error fetching custom labels: {:?}", err);
Err("Error fetching custom labels".into())
}
}
} }
pub async fn get_id_by_value(value: &String) -> Result<i32, sqlx::Error> { pub async fn get_id_by_value(value: &String) -> Result<i32, String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => return Err(reason),
};
let result = sqlx::query!( let result = sqlx::query!(
"SELECT custom_label_id FROM custom_label WHERE custom_label_value = ?", "SELECT custom_label_id FROM custom_label WHERE custom_label_value = ?",
value value
) )
.fetch_all(db) .fetch_all(db)
.await?; .await;
let result = match result {
Ok(r) => r,
Err(err) => {
log::error!("Error fetching label by value: {:?}", err);
return Err("Error fetching label by value".into());
}
};
if result.is_empty() { if result.is_empty() {
Ok(-1) Ok(-1)
@ -42,15 +62,24 @@ impl CustomLabel {
} }
} }
pub async fn create(value: &String) -> Result<i32, sqlx::Error> { pub async fn create(value: &String) -> Result<i32, String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => return Err(reason),
};
sqlx::query!( let result = sqlx::query!(
"INSERT INTO custom_label (custom_label_value) VALUES (?)", "INSERT INTO custom_label (custom_label_value) VALUES (?)",
value value
) )
.execute(db) .execute(db)
.await?; .await;
if let Err(err) = result {
log::error!("Error inserting custom label: {:?}", err);
return Err("Error inserting custom label".into());
}
let result = Self::get_id_by_value(value).await?; let result = Self::get_id_by_value(value).await?;
Ok(result) Ok(result)

View File

@ -4,3 +4,8 @@ pub mod custom_label;
pub mod person; pub mod person;
pub mod register; pub mod register;
pub mod reniec_person; pub mod reniec_person;
pub enum DBError {
Str(String),
Sqlx(sqlx::Error),
}

View File

@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
use crate::db; use crate::db;
use super::DBError;
/// Information about a person registered in DB /// Information about a person registered in DB
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
pub struct Person { pub struct Person {
@ -31,8 +33,11 @@ pub struct Person {
} }
impl Person { impl Person {
pub async fn get_by_dni(dni: i32) -> Result<Person, sqlx::Error> { pub async fn get_by_dni(dni: i32) -> Result<Person, DBError> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => return Err(DBError::Str(reason)),
};
let result = sqlx::query_as!(Person, "SELECT * FROM person WHERE person_dni = ?", dni) let result = sqlx::query_as!(Person, "SELECT * FROM person WHERE person_dni = ?", dni)
.fetch_one(db) .fetch_one(db)
@ -42,7 +47,7 @@ impl Person {
Ok(v) => Ok(v), Ok(v) => Ok(v),
Err(e) => { Err(e) => {
error!("Error searching person with dni {}: {:?}", dni, e); error!("Error searching person with dni {}: {:?}", dni, e);
Err(e) Err(DBError::Sqlx(e))
} }
} }
} }
@ -57,10 +62,13 @@ pub struct PersonCreate {
} }
impl PersonCreate { impl PersonCreate {
pub async fn create(&self) -> Result<(), sqlx::Error> { pub async fn create(&self) -> Result<(), String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(err) => return Err(err),
};
sqlx::query!( let result = sqlx::query!(
"INSERT INTO person (person_dni, person_names, person_paternal_surname, person_maternal_surname) VALUES (?, ?, ?, ?)", "INSERT INTO person (person_dni, person_names, person_paternal_surname, person_maternal_surname) VALUES (?, ?, ?, ?)",
self.person_dni, self.person_dni,
self.person_names, self.person_names,
@ -68,9 +76,15 @@ impl PersonCreate {
self.person_maternal_surname, self.person_maternal_surname,
) )
.execute(db) .execute(db)
.await?; .await;
Ok(()) match result {
Ok(_) => Ok(()),
Err(err) => {
log::error!("Error creating person: {:?}", err);
Err("Error creating person".into())
}
}
} }
} }
@ -84,7 +98,10 @@ pub struct PersonLink {
impl PersonLink { impl PersonLink {
/// Links a person to a user in the online classroom /// Links a person to a user in the online classroom
pub async fn insert(&self) -> Result<(), String> { pub async fn insert(&self) -> Result<(), String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => return Err(reason),
};
let res = sqlx::query!( let res = sqlx::query!(
"UPDATE person SET person_classroom_id = ?, person_classroom_username = ? WHERE person_id = ?", "UPDATE person SET person_classroom_id = ?, person_classroom_username = ? WHERE person_id = ?",

View File

@ -31,8 +31,11 @@ pub struct RegisterCreate {
impl RegisterCreate { impl RegisterCreate {
/// Registers a new certificate /// Registers a new certificate
pub async fn create(&self) -> Result<(), sqlx::Error> { pub async fn create(&self) -> Result<(), String> {
let db = db().await; let db = match db().await {
Ok(db) => db,
Err(reason) => return Err(reason),
};
// Get custom_label_id from db based of self.custom_label // Get custom_label_id from db based of self.custom_label
let custom_label_id = { let custom_label_id = {
@ -57,7 +60,7 @@ impl RegisterCreate {
// Current date in YYYY-MM-DD format // Current date in YYYY-MM-DD format
let current_date = chrono::Local::now().format("%Y-%m-%d").to_string(); let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();
let _ = sqlx::query!( let result = sqlx::query!(
"INSERT INTO register ( "INSERT INTO register (
register_code, register_code,
register_creation_date, register_creation_date,
@ -76,15 +79,21 @@ impl RegisterCreate {
self.course_id self.course_id
) )
.execute(db) .execute(db)
.await?; .await;
Ok(()) match result {
Ok(_) => Ok(()),
Err(err) => {
log::error!("Error inserting person: {:?}", err);
Err("Error inserting person".into())
}
}
} }
async fn get_next_register_code(course_id: i32) -> Result<i32, sqlx::Error> { async fn get_next_register_code(course_id: i32) -> Result<i32, String> {
let db = db().await; let db = db().await?;
let course_name = Course::get_course_name(course_id).await; let course_name = Course::get_course_name(course_id).await?;
// All matpel certs share codes // All matpel certs share codes
let new_max = if course_name.contains("Matpel") { let new_max = if course_name.contains("Matpel") {
@ -94,10 +103,18 @@ impl RegisterCreate {
(SELECT course_id FROM course WHERE course_name LIKE 'Matpel%')", (SELECT course_id FROM course WHERE course_name LIKE 'Matpel%')",
) )
.fetch_one(db) .fetch_one(db)
.await?; .await;
// If there are no registers, the new code will start from 50, as per the requirements match res {
res.max.unwrap_or(50) + 1 Ok(res) => {
// If there are no registers, the new code will start from 50, as per the requirements
res.max.unwrap_or(50) + 1
}
Err(err) => {
log::error!("Error fetching matpel code: {:?}", err);
return Err("Error fetching matpel code".into());
}
}
} else { } else {
let res = sqlx::query!( let res = sqlx::query!(
"SELECT MAX(register_code) AS max FROM register "SELECT MAX(register_code) AS max FROM register
@ -105,10 +122,18 @@ impl RegisterCreate {
course_id course_id
) )
.fetch_one(db) .fetch_one(db)
.await?; .await;
// If there are no registers, the new code will start from 50, as per the requirements match res {
res.max.unwrap_or(50) + 1 Ok(res) => {
// If there are no registers, the new code will start from 50, as per the requirements
res.max.unwrap_or(50) + 1
}
Err(err) => {
log::error!("Error fetching cert code: {:?}", err);
return Err("Error fetching cert code".into());
}
}
}; };
Ok(new_max) Ok(new_max)
@ -139,8 +164,8 @@ pub struct Register {
} }
impl Register { impl Register {
pub async fn get_by_dni(dni: String) -> Result<Vec<Register>, sqlx::Error> { pub async fn get_by_dni(dni: String) -> Result<Vec<Register>, String> {
let db = db().await; let db = db().await?;
let res = sqlx::query!( let res = sqlx::query!(
"SELECT * FROM register "SELECT * FROM register
@ -148,38 +173,54 @@ impl Register {
dni dni
) )
.fetch_all(db) .fetch_all(db)
.await? .await;
.into_iter()
.map(|r| Register { let res = match res {
register_id: r.register_id, Ok(res) => res,
register_code: r.register_code, Err(err) => {
register_creation_date: r.register_creation_date.format("%Y-%m-%d").to_string(), log::error!("Error fetching register for DNI: {:?}", err);
register_display_date: r.register_display_date.format("%Y-%m-%d").to_string(), return Err("Error fetching register for DNI".into());
register_custom_label: r.register_custom_label, }
register_is_preview: r.register_is_preview == 1, };
register_person_id: r.register_person_id,
register_course_id: r.register_course_id, let res = res
}) .into_iter()
.collect(); .map(|r| Register {
register_id: r.register_id,
register_code: r.register_code,
register_creation_date: r.register_creation_date.format("%Y-%m-%d").to_string(),
register_display_date: r.register_display_date.format("%Y-%m-%d").to_string(),
register_custom_label: r.register_custom_label,
register_is_preview: r.register_is_preview == 1,
register_person_id: r.register_person_id,
register_course_id: r.register_course_id,
})
.collect();
Ok(res) Ok(res)
} }
pub async fn delete(register_id: i32) -> Result<(), sqlx::Error> { pub async fn delete(register_id: i32) -> Result<(), String> {
let db = db().await; let db = db().await?;
let _ = sqlx::query!("DELETE FROM register WHERE register_id = ?", register_id) let res = sqlx::query!("DELETE FROM register WHERE register_id = ?", register_id)
.execute(db) .execute(db)
.await?; .await;
Ok(()) match res {
Ok(_) => Ok(()),
Err(err) => {
log::error!("Error deleting register: {:?}", err);
Err("Error deleting register".into())
}
}
} }
pub async fn get_course_and_person( pub async fn get_course_and_person(
register_id_list: String, register_id_list: String,
person_dni_list: String, person_dni_list: String,
) -> Result<Vec<ScanData>, sqlx::Error> { ) -> Result<Vec<ScanData>, String> {
let db = db().await; let db = db().await?;
let sql = format!( let sql = format!(
" "
@ -209,7 +250,13 @@ impl Register {
log::info!("sql: {}", sql); log::info!("sql: {}", sql);
let result = db.fetch_all(sql.as_str()).await?; let result = match db.fetch_all(sql.as_str()).await {
Ok(res) => res,
Err(err) => {
log::error!("Error fetching course & person: {:?}", err);
return Err("Error fetching course & person".into());
}
};
log::info!("rows: {}", result.len()); log::info!("rows: {}", result.len());

View File

@ -61,10 +61,9 @@ pub async fn create(data: &ClassroomPersonCreate) -> Result<PersonLink, String>
Err("Expected empty body, found something else".into()) Err("Expected empty body, found something else".into())
} }
/// Makes a request to the user creation form, and gets the /// Makes a request to the user creation form, and gets the
/// security token. /// security token.
/// ///
/// This security token must be present for CSRF reasons /// This security token must be present for CSRF reasons
async fn get_form_sec_token() -> Result<String, String> { async fn get_form_sec_token() -> Result<String, String> {
let creation_form = request("/main/admin/user_add.php".into()).await?; let creation_form = request("/main/admin/user_add.php".into()).await?;
@ -91,7 +90,6 @@ async fn get_form_sec_token() -> Result<String, String> {
Ok(sec_token_value.into()) Ok(sec_token_value.into())
} }
/// Creates a request body data to send to the classroom /// Creates a request body data to send to the classroom
fn get_request_body( fn get_request_body(
surnames: &String, surnames: &String,

View File

@ -12,12 +12,11 @@ mod session;
pub mod update_expiration_date; pub mod update_expiration_date;
pub mod user; pub mod user;
/// Tries to connect to the online classroom, and gets a session cookie /// Tries to connect to the online classroom, and gets a session cookie
/// if neccesary. /// if neccesary.
/// ///
/// Mantains a global session cookie for ~20 minutes. /// Mantains a global session cookie for ~20 minutes.
/// ///
/// Every time a request is made the session cookie is checked. If more than /// Every time a request is made the session cookie is checked. If more than
/// 20 minutes have passed, a new session cookie is requested. /// 20 minutes have passed, a new session cookie is requested.
#[get("/classroom/connect")] #[get("/classroom/connect")]

View File

@ -166,7 +166,7 @@ pub async fn register_courses_request(url: String, body: String) -> Result<Strin
} }
/// Makes sure that the session cookie is set, and that it is valid. /// Makes sure that the session cookie is set, and that it is valid.
/// ///
/// If the cookie has expired, get a new one /// If the cookie has expired, get a new one
pub async fn ensure_session() -> Result<(), String> { pub async fn ensure_session() -> Result<(), String> {
let last_usage_time = SESSION_TIME.read().unwrap().clone(); let last_usage_time = SESSION_TIME.read().unwrap().clone();
@ -238,7 +238,6 @@ async fn login() -> Result<(), String> {
} }
} }
/// Helper function to log the responses recieved from the online classroom, /// Helper function to log the responses recieved from the online classroom,
/// for debugging purposes /// for debugging purposes
pub fn log_html(html: &String) { pub fn log_html(html: &String) {