[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>")] #[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(); let data = data.into_inner();
match convert_scans_from_data(&data).await { 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)), 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. /// Converts a list of files into PDFs.
/// ///
/// Uses the timestamps inside `data` to read the correct JPG files and convert them. /// 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. // Get a tuple with all the DNIs & iids.
let (ids, dnis) = data.iter().fold( let (ids, dnis) = data.iter().fold(
(vec!["-1".to_string()], vec!["''".to_string()]), (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, // Match the results from DB with the data from the frontend,
// & convert the files to PDFs // & convert the files to PDFs
for info in data { for info in data {
@ -334,6 +337,10 @@ async fn convert_scans_from_data(data: &Vec<ScanInfo>) -> Result<(), String> {
Some(p) => (p.clone(), timestamp), Some(p) => (p.clone(), timestamp),
None => { None => {
log::error!("Register not found in data from DB: id {}, dni {}", id, dni); 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; continue;
} }
} }
@ -346,6 +353,10 @@ async fn convert_scans_from_data(data: &Vec<ScanInfo>) -> Result<(), String> {
Some(p) => (p.clone(), timestamp), Some(p) => (p.clone(), timestamp),
None => { None => {
log::error!("Register not found in data from DB: dni {}", dni); 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; continue;
} }
} }
@ -364,7 +375,7 @@ async fn convert_scans_from_data(data: &Vec<ScanInfo>) -> Result<(), String> {
(scan_data, timestamp) (scan_data, timestamp)
} }
ScanInfo::Error(reason) => { ScanInfo::Error(reason) => {
log::info!("Found an error while matching data with DB: {}", reason); log::info!("Tried to process an errored cert: {}", reason);
continue; continue;
} }
}; };
@ -374,6 +385,10 @@ async fn convert_scans_from_data(data: &Vec<ScanInfo>) -> Result<(), String> {
Ok(t) => t, Ok(t) => t,
Err(err) => { Err(err) => {
log::error!("Error obteniendo hora actual: {:?}", err); log::error!("Error obteniendo hora actual: {:?}", err);
errors.push(format!(
"Error conviritendo archivo {}: Error obteniendo hora actual.",
timestamp
));
continue; continue;
} }
}; };
@ -401,17 +416,25 @@ async fn convert_scans_from_data(data: &Vec<ScanInfo>) -> Result<(), String> {
Ok(_) => log::info!("Deleted image for: {}", filename), Ok(_) => log::info!("Deleted image for: {}", filename),
Err(err) => { Err(err) => {
log::error!("Error deleting image for {}: {:?}", filename, err); log::error!("Error deleting image for {}: {:?}", filename, err);
errors.push(format!(
"Error eliminando archivo jpg {}: {}",
timestamp, err
));
} }
} }
} }
Err(reason) => { Err(reason) => {
log::error!("Error converting to pdf: {}", reason); log::error!("Error converting to pdf: {}", reason);
errors.push(format!(
"Error convirtiendo a PDF {}: {}",
timestamp, reason
));
continue; continue;
} }
} }
} }
Ok(()) Ok(errors)
} }
/// Converts a file to PDF using Imagemagick /// Converts a file to PDF using Imagemagick

View File

@ -1,10 +1,10 @@
use cors::Cors; use cors::Cors;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use sqlx::mysql::MySqlPoolOptions;
use sqlx::Connection; use sqlx::Connection;
use sqlx::MySql; use sqlx::MySql;
use sqlx::MySqlConnection; use sqlx::MySqlConnection;
use sqlx::Pool; use sqlx::Pool;
use sqlx::mysql::MySqlPoolOptions;
use std::env; use std::env;
use std::time::Instant; 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 active connections: {}", db.size());
log::info!("DB num_idle connections: {}", db.num_idle()); log::info!("DB num_idle connections: {}", db.num_idle());
return Ok(db) return Ok(db);
}, }
None => { None => {
log::info!("DB not initialized, initializing from db()"); log::info!("DB not initialized, initializing from db()");
let _ = init_db().await; 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 On some afternoons for some god forsaken reason the idle connections
to the db stay alive, but stop responding. to the db stay alive, but stop responding.
When this happens, we must restart the server, or wait for all When this happens, we must restart the server, or wait for all
the active connections to timeout. the active connections to timeout.
Here are some measures to circumvent that: Here are some measures to circumvent that:
*/ */
// Set the maximum wait time for connections to 10 seconds // 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)) .idle_timeout(std::time::Duration::from_secs(10 * 60))
.connect(db_url.as_str()) .connect(db_url.as_str())
.await; .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 { match pool {
Ok(pool) => match DB.set(pool) { Ok(pool) => match DB.set(pool) {

View File

@ -35,9 +35,7 @@ impl Course {
} }
}; };
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 { let results = match results {
Ok(res) => res, 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 { ScanResult } from ".";
import { FilledButton } from "../components/FilledButton"; import { FilledButton } from "../components/FilledButton";
import { FilledCard } from "../components/FilledCard"; import { FilledCard } from "../components/FilledCard";
@ -11,6 +11,7 @@ import { JsonResult } from "../types/JsonResult";
export function ScansList(props: {scanData: Array<ScanResult> | null, onSuccess: () => void}) { export function ScansList(props: {scanData: Array<ScanResult> | null, onSuccess: () => void}) {
const {status, setStatus, error, setError} = useLoading(); const {status, setStatus, error, setError} = useLoading();
const [convertionResult, setConvertionResult] = createSignal<Array<string> | null>(null);
const loading = createMemo(() => status() === LoadingStatus.Loading); const loading = createMemo(() => status() === LoadingStatus.Loading);
@ -40,6 +41,7 @@ export function ScansList(props: {scanData: Array<ScanResult> | null, onSuccess:
const convertScans = () => { const convertScans = () => {
setStatus(LoadingStatus.Loading); setStatus(LoadingStatus.Loading);
setConvertionResult(null);
if (props.scanData === null) { if (props.scanData === null) {
setError("No se detectaron escaneos"); 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) axios.post(`${import.meta.env.VITE_BACKEND_URL}/api/scans/convert`, props.scanData)
.then(() => { .then((response) => {
setStatus(LoadingStatus.Ok); setStatus(LoadingStatus.Ok);
setConvertionResult(response?.data.Ok ?? []);
props.onSuccess(); props.onSuccess();
}) })
.catch((err) => { .catch((err) => {
@ -166,6 +169,29 @@ export function ScansList(props: {scanData: Array<ScanResult> | null, onSuccess:
</Show> </Show>
</FilledCard> </FilledCard>
</Show> </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() { export function Scans() {
const { status, setStatus, error, setError } = useLoading(); const { status, setStatus, error, setError } = useLoading();
const [scanData, setScanData] = createSignal<Array<ScanResult> | null>(null); const [scanData, setScanData] = createSignal<Array<ScanResult> | null>(null);
const [converted, setConverted] = createSignal(false);
const loading = createMemo(() => status() === LoadingStatus.Loading); const loading = createMemo(() => status() === LoadingStatus.Loading);
const detectScans = () => { const detectScans = () => {
setStatus(LoadingStatus.Loading); setStatus(LoadingStatus.Loading);
setScanData(null); setScanData(null);
setConverted(false);
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/scans/detect`) fetch(`${import.meta.env.VITE_BACKEND_URL}/api/scans/detect`)
.then((res) => res.json()) .then((res) => res.json())
@ -179,18 +178,9 @@ export function Scans() {
</FilledCard> </FilledCard>
<ScansList scanData={scanData()} onSuccess={() => { <ScansList scanData={scanData()} onSuccess={() => {
setConverted(true);
setScanData(null); 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>
</div> </div>
); );