[Scans] Fixes #26: Generate & show a list of errors for individual scans

master
Araozu 2023-12-22 10:49:09 -05:00
parent 12093b88c3
commit bc69762fb0
5 changed files with 65 additions and 27 deletions

View File

@ -54,11 +54,11 @@ pub fn convert_scans_options() -> Status {
}
#[post("/scans/convert", format = "json", data = "<data>")]
pub async fn convert_scans(data: Json<Vec<ScanInfo>>) -> (Status, Json<JsonResult<()>>) {
pub async fn convert_scans(data: Json<Vec<ScanInfo>>) -> (Status, Json<JsonResult<Vec<String>>>) {
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<ScanInfo>) -> Result<(), String> {
async fn convert_scans_from_data(data: &Vec<ScanInfo>) -> Result<Vec<String>, 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<ScanInfo>) -> Result<(), String> {
}
};
// List of non-fatal errors
let mut errors = Vec::<String>::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<ScanInfo>) -> 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<ScanInfo>) -> 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<ScanInfo>) -> 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<ScanInfo>) -> 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<ScanInfo>) -> 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

View File

@ -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<MySql>, 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) {

View File

@ -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,

View File

@ -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<ScanResult> | null, onSuccess: () => void}) {
const {status, setStatus, error, setError} = useLoading();
const [convertionResult, setConvertionResult] = createSignal<Array<string> | null>(null);
const loading = createMemo(() => status() === LoadingStatus.Loading);
@ -40,6 +41,7 @@ export function ScansList(props: {scanData: Array<ScanResult> | 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<ScanResult> | 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<ScanResult> | null, onSuccess:
</Show>
</FilledCard>
</Show>
<Show when={convertionResult() !== null && convertionResult()!.length === 0}>
<FilledCard>
<h1 class="py-2 px-4 font-medium text-xl mb-2">
Archivos convertidos con éxito
</h1>
</FilledCard>
</Show>
<Show when={convertionResult() !== null && convertionResult()!.length > 0}>
<FilledCard>
<h1 class="py-2 px-4 font-medium text-xl mb-2">
Archivos convertidos con errores
</h1>
<div class="bg-c-surface p-4">
<p class="my-2">Se encontraron errores convirtiendo {convertionResult()!.length} archivos:</p>
<For each={convertionResult()!}>
{(error) => <p class="my-1 text-c-error">- {error}</p>}
</For>
</div>
</FilledCard>
</Show>
</>
);
}

View File

@ -25,14 +25,13 @@ export type ScanResult =
export function Scans() {
const { status, setStatus, error, setError } = useLoading();
const [scanData, setScanData] = createSignal<Array<ScanResult> | 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() {
</FilledCard>
<ScansList scanData={scanData()} onSuccess={() => {
setConverted(true);
setScanData(null);
}}
/>
<Show when={converted()}>
<FilledCard>
<h1 class="py-2 px-4 font-medium text-xl mb-2">
Archivos convertidos con éxito
</h1>
</FilledCard>
</Show>
</div>
</div>
);