[Scans] Fixes #26: Generate & show a list of errors for individual scans
This commit is contained in:
parent
12093b88c3
commit
bc69762fb0
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user