diff --git a/backend/src/controller/scans/mod.rs b/backend/src/controller/scans/mod.rs index d65d869..1f3df4d 100644 --- a/backend/src/controller/scans/mod.rs +++ b/backend/src/controller/scans/mod.rs @@ -54,11 +54,11 @@ pub fn convert_scans_options() -> Status { } #[post("/scans/convert", format = "json", data = "")] -pub async fn convert_scans(data: Json>) -> (Status, Json>) { +pub async fn convert_scans(data: Json>) -> (Status, Json>>) { let data = data.into_inner(); match convert_scans_from_data(&data).await { - Ok(_) => (Status::Ok, JsonResult::ok(())), + Ok(error_list) => (Status::Ok, JsonResult::ok(error_list)), Err(reason) => (Status::InternalServerError, JsonResult::err(reason)), } } @@ -289,7 +289,7 @@ fn get_image_info(path: PathBuf) -> ScanInfo { /// Converts a list of files into PDFs. /// /// Uses the timestamps inside `data` to read the correct JPG files and convert them. -async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { +async fn convert_scans_from_data(data: &Vec) -> Result, String> { // Get a tuple with all the DNIs & iids. let (ids, dnis) = data.iter().fold( (vec!["-1".to_string()], vec!["''".to_string()]), @@ -324,6 +324,9 @@ async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { } }; + // List of non-fatal errors + let mut errors = Vec::::new(); + // Match the results from DB with the data from the frontend, // & convert the files to PDFs for info in data { @@ -334,6 +337,10 @@ async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { Some(p) => (p.clone(), timestamp), None => { log::error!("Register not found in data from DB: id {}, dni {}", id, dni); + errors.push(format!( + "Certificado no encontrado en la base de datos: id {}, dni {}, timestamp {}", + id, dni, timestamp + )); continue; } } @@ -346,6 +353,10 @@ async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { Some(p) => (p.clone(), timestamp), None => { log::error!("Register not found in data from DB: dni {}", dni); + errors.push(format!( + "Certificado no encontrado en la base de datos: dni {}, timestamp {}", + dni, timestamp + )); continue; } } @@ -364,7 +375,7 @@ async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { (scan_data, timestamp) } ScanInfo::Error(reason) => { - log::info!("Found an error while matching data with DB: {}", reason); + log::info!("Tried to process an errored cert: {}", reason); continue; } }; @@ -374,6 +385,10 @@ async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { Ok(t) => t, Err(err) => { log::error!("Error obteniendo hora actual: {:?}", err); + errors.push(format!( + "Error conviritendo archivo {}: Error obteniendo hora actual.", + timestamp + )); continue; } }; @@ -401,17 +416,25 @@ async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { Ok(_) => log::info!("Deleted image for: {}", filename), Err(err) => { log::error!("Error deleting image for {}: {:?}", filename, err); + errors.push(format!( + "Error eliminando archivo jpg {}: {}", + timestamp, err + )); } } } Err(reason) => { log::error!("Error converting to pdf: {}", reason); + errors.push(format!( + "Error convirtiendo a PDF {}: {}", + timestamp, reason + )); continue; } } } - Ok(()) + Ok(errors) } /// Converts a file to PDF using Imagemagick diff --git a/backend/src/main.rs b/backend/src/main.rs index 9777f7b..ded8079 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,10 +1,10 @@ use cors::Cors; use once_cell::sync::OnceCell; +use sqlx::mysql::MySqlPoolOptions; use sqlx::Connection; use sqlx::MySql; use sqlx::MySqlConnection; use sqlx::Pool; -use sqlx::mysql::MySqlPoolOptions; use std::env; use std::time::Instant; @@ -55,8 +55,8 @@ pub async fn db() -> Result<&'static Pool, String> { log::info!("DB active connections: {}", db.size()); log::info!("DB num_idle connections: {}", db.num_idle()); - return Ok(db) - }, + return Ok(db); + } None => { log::info!("DB not initialized, initializing from db()"); let _ = init_db().await; @@ -83,10 +83,8 @@ pub async fn init_db() -> Result<(), String> { /* On some afternoons for some god forsaken reason the idle connections to the db stay alive, but stop responding. - When this happens, we must restart the server, or wait for all the active connections to timeout. - Here are some measures to circumvent that: */ // Set the maximum wait time for connections to 10 seconds @@ -96,7 +94,10 @@ pub async fn init_db() -> Result<(), String> { .idle_timeout(std::time::Duration::from_secs(10 * 60)) .connect(db_url.as_str()) .await; - log::info!("DB Pool connection took: {:?} ms", start.elapsed().as_millis()); + log::info!( + "DB Pool connection took: {:?} ms", + start.elapsed().as_millis() + ); match pool { Ok(pool) => match DB.set(pool) { diff --git a/backend/src/model/course.rs b/backend/src/model/course.rs index 0fe6d69..1251834 100644 --- a/backend/src/model/course.rs +++ b/backend/src/model/course.rs @@ -35,9 +35,7 @@ impl Course { } }; - let results = sqlx::query!("SELECT * FROM course") - .fetch_all(db) - .await; + let results = sqlx::query!("SELECT * FROM course").fetch_all(db).await; let results = match results { Ok(res) => res, diff --git a/frontend/src/Scans/ScansList.tsx b/frontend/src/Scans/ScansList.tsx index 12d8a3c..340b27b 100644 --- a/frontend/src/Scans/ScansList.tsx +++ b/frontend/src/Scans/ScansList.tsx @@ -1,4 +1,4 @@ -import { Show, createMemo } from "solid-js"; +import { For, Show, createMemo, createSignal } from "solid-js"; import { ScanResult } from "."; import { FilledButton } from "../components/FilledButton"; import { FilledCard } from "../components/FilledCard"; @@ -11,6 +11,7 @@ import { JsonResult } from "../types/JsonResult"; export function ScansList(props: {scanData: Array | null, onSuccess: () => void}) { const {status, setStatus, error, setError} = useLoading(); + const [convertionResult, setConvertionResult] = createSignal | null>(null); const loading = createMemo(() => status() === LoadingStatus.Loading); @@ -40,6 +41,7 @@ export function ScansList(props: {scanData: Array | null, onSuccess: const convertScans = () => { setStatus(LoadingStatus.Loading); + setConvertionResult(null); if (props.scanData === null) { setError("No se detectaron escaneos"); @@ -48,8 +50,9 @@ export function ScansList(props: {scanData: Array | null, onSuccess: } axios.post(`${import.meta.env.VITE_BACKEND_URL}/api/scans/convert`, props.scanData) - .then(() => { + .then((response) => { setStatus(LoadingStatus.Ok); + setConvertionResult(response?.data.Ok ?? []); props.onSuccess(); }) .catch((err) => { @@ -166,6 +169,29 @@ export function ScansList(props: {scanData: Array | null, onSuccess: + + + +

+ Archivos convertidos con éxito +

+
+
+ + 0}> + +

+ Archivos convertidos con errores +

+ +
+

Se encontraron errores convirtiendo {convertionResult()!.length} archivos:

+ + {(error) =>

- {error}

} +
+
+
+
); } diff --git a/frontend/src/Scans/index.tsx b/frontend/src/Scans/index.tsx index 3e6374d..693abd8 100644 --- a/frontend/src/Scans/index.tsx +++ b/frontend/src/Scans/index.tsx @@ -25,14 +25,13 @@ export type ScanResult = export function Scans() { const { status, setStatus, error, setError } = useLoading(); const [scanData, setScanData] = createSignal | null>(null); - const [converted, setConverted] = createSignal(false); + const loading = createMemo(() => status() === LoadingStatus.Loading); const detectScans = () => { setStatus(LoadingStatus.Loading); setScanData(null); - setConverted(false); fetch(`${import.meta.env.VITE_BACKEND_URL}/api/scans/detect`) .then((res) => res.json()) @@ -179,18 +178,9 @@ export function Scans() { { - setConverted(true); setScanData(null); }} /> - - - -

- Archivos convertidos con éxito -

-
-
);