From 605ee27b4fd2bcc7a885f73db8e454fbed1fb722 Mon Sep 17 00:00:00 2001 From: Araozu Date: Thu, 16 Nov 2023 10:35:30 -0500 Subject: [PATCH] [BE] Detect QR, parse params & get data from DB --- backend/src/controller/scans/mod.rs | 78 +++++++++++++++++++- backend/src/main.rs | 2 + backend/src/model/register.rs | 66 ++++++++++++++++- frontend/src/Scans/index.tsx | 72 +++++++++++++++++- frontend/src/certGenerator/certs/CertData.ts | 2 +- frontend/src/certGenerator/certs/utils.ts | 3 +- frontend/src/icons/PDFIcon.tsx | 9 +++ 7 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 frontend/src/icons/PDFIcon.tsx diff --git a/backend/src/controller/scans/mod.rs b/backend/src/controller/scans/mod.rs index 93d71e6..18f1bdb 100644 --- a/backend/src/controller/scans/mod.rs +++ b/backend/src/controller/scans/mod.rs @@ -1,19 +1,20 @@ -use crate::json_result::JsonResult; +use crate::{json_result::JsonResult, model::register::Register}; use printpdf::{Mm, PdfDocument}; use rocket::{http::Status, serde::json::Json}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{ f32::consts::E, fs::{self, File}, io::BufWriter, path::PathBuf, + result, time::{SystemTime, UNIX_EPOCH}, }; const SCAN_PATH: &str = "/srv/srv/shares/eegsac/ESCANEOS/"; /// Represents the result of parsing an eegsac URL -#[derive(Serialize)] +#[derive(Serialize, Deserialize)] enum ScanInfo { /// The url has both DNI & id Full(String, i32, String), @@ -38,6 +39,32 @@ pub async fn detect_scans() -> (Status, Json (Status::Ok, JsonResult::ok(file_details)) } +#[options("/scans/convert")] +pub fn convert_scans_options() -> Status { + Status::Ok +} + +#[post("/scans/convert", format = "json", data = "")] +pub async fn convert_scans( + data: Json>>, +) -> (Status, Json>) { + let data = data.into_inner(); + let data = match data { + JsonResult::Ok(v) => v, + JsonResult::Error(err) => { + return ( + Status::BadRequest, + JsonResult::err("Se recibió un error.".into()), + ) + } + }; + + match convert_scans_from_data(&data).await { + Ok(_) => (Status::Ok, JsonResult::ok(())), + Err(reason) => (Status::InternalServerError, JsonResult::err(reason)), + } +} + /// Reads the SCAN_PATH directory and returns a list of file paths /// that are valid for processing. fn get_valid_files() -> Result, String> { @@ -131,7 +158,7 @@ fn get_image_info(path: PathBuf) -> ScanInfo { let width = img.width(); let height = img.height(); - let third_point_height = (height / 3) * 2; + let third_point_height = (height / 4) * 3; let mid_point_width = width / 2; let remaining_height = height - third_point_height; @@ -141,6 +168,11 @@ fn get_image_info(path: PathBuf) -> ScanInfo { // get qr from cropped image let results = bardecoder::default_decoder().decode(&cropped_img); + if results.is_empty() { + eprintln!("QR not found"); + return ScanInfo::Error("QR no encontrado.".into()); + } + let url = match &results[0] { Ok(url) => url, Err(reason) => { @@ -201,3 +233,41 @@ fn get_image_info(path: PathBuf) -> ScanInfo { } } } + +// TODO: this should return a list of files that succeeded & failed +async fn convert_scans_from_data(data: &Vec<(PathBuf, ScanInfo)>) -> Result<(), String> { + let (ids, dnis) = data.iter().fold( + (vec!["-1".to_string()], vec!["''".to_string()]), + |mut acc, (_, next)| { + match next { + ScanInfo::Full(_, id, _) => acc.0.push(id.to_string()), + ScanInfo::Partial(dni, _) => acc.1.push(format!("'{}'", dni)), + _ => (), + }; + + acc + }, + ); + + let register_id_list = ids.join(","); + let person_dni_list = dnis.join("-"); + + log::info!("register_id_list: {}", register_id_list); + log::info!("person_dni_list: {}", person_dni_list); + + let persons_info = Register::get_course_and_person(register_id_list, person_dni_list).await; + + let persons_info = match persons_info { + Ok(v) => v, + Err(err) => { + eprintln!("Error getting persons info: {:?}", err); + return Err("Error obteniendo información de personas.".into()); + } + }; + + for e in persons_info { + println!("\n{:?}", e); + } + + Ok(()) +} diff --git a/backend/src/main.rs b/backend/src/main.rs index f3eaf84..aba6677 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -72,6 +72,8 @@ async fn rocket() -> _ { // Scans routes // controller::scans::detect_scans, + controller::scans::convert_scans_options, + controller::scans::convert_scans, ], ) } diff --git a/backend/src/model/register.rs b/backend/src/model/register.rs index 50d40ed..7201293 100644 --- a/backend/src/model/register.rs +++ b/backend/src/model/register.rs @@ -1,14 +1,24 @@ use rocket::form::validate::Contains; use serde::{Deserialize, Serialize}; +use sqlx::{Executor, Row}; use crate::db; use super::{course::Course, custom_label::CustomLabel}; #[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(crate = "rocket::serde")] +pub struct ScanData { + pub course_name: String, + pub person_names: String, + pub person_paternal_surname: String, + pub person_maternal_surname: String, + pub person_dni: String, + pub register_id: i32, +} + /// Represents a single register send by the frontend /// to create a new register in the database +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct RegisterCreate { person_id: i32, course_id: i32, @@ -161,4 +171,58 @@ impl Register { Ok(()) } + + pub async fn get_course_and_person( + register_id_list: String, + person_dni_list: String, + ) -> Result, sqlx::Error> { + let db = db(); + + let sql = format!( + " + select + course.course_name, + register.register_id, + person.person_dni, + person.person_names, + person.person_paternal_surname, + person.person_maternal_surname + from register + inner join course on course.course_id = register.register_course_id + inner join person on person.person_id = register.register_person_id + where register_id IN ({register_id_list}) + union + select + '' as 'course_name', + 0 as register_id, + '' as person_dni, + person.person_names, + person.person_paternal_surname, + person.person_maternal_surname + from person + where person_dni IN ({person_dni_list}) + " + ); + + log::info!("sql: {}", sql); + + let result = db.fetch_all(sql.as_str()).await?; + + log::info!("rows: {}", result.len()); + + let data = result + .iter() + .map(|row| ScanData { + course_name: row.try_get("course_name").unwrap(), + person_names: row.try_get("person_names").unwrap(), + register_id: row.try_get("register_id").unwrap(), + person_dni: row.try_get("person_dni").unwrap(), + person_paternal_surname: row.try_get("person_paternal_surname").unwrap(), + person_maternal_surname: row.try_get("person_maternal_surname").unwrap(), + }) + .collect(); + + Ok(data) + } } + diff --git a/frontend/src/Scans/index.tsx b/frontend/src/Scans/index.tsx index 8ef9c16..9878cc7 100644 --- a/frontend/src/Scans/index.tsx +++ b/frontend/src/Scans/index.tsx @@ -4,6 +4,7 @@ import { FilledCard } from "../components/FilledCard"; import { LoadingIcon } from "../icons/LoadingIcon"; import { MagnifyingGlassIcon } from "../icons/MagnifyingGlassIcon"; import { LoadingStatus, useLoading } from "../utils/functions"; +import { PDFIcon } from "../icons/PDFIcon"; /** * Represents the data about the scans that were detected. @@ -36,6 +37,7 @@ export function Scans() { const detectScans = () => { setStatus(LoadingStatus.Loading); + setScanData(null); fetch(`${import.meta.env.VITE_BACKEND_URL}/api/scans/detect`) .then((res) => res.json()) @@ -49,6 +51,37 @@ export function Scans() { }); }; + const convertScans = () => { + setStatus(LoadingStatus.Loading); + + if (scanData() === null) { + setError("No se detectaron escaneos"); + setStatus(LoadingStatus.Error); + return; + } + + + fetch( + `${import.meta.env.VITE_BACKEND_URL}/api/scans/convert`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(scanData()), + }, + ) + .then((res) => res.json()) + .then((res) => { + setStatus(LoadingStatus.Ok); + console.log(res); + }) + .catch((err) => { + setStatus(LoadingStatus.Error); + console.error(err); + }); + }; + return (
@@ -126,6 +159,7 @@ export function Scans() {

- Al convertir los escaneos a PDF, se eliminan los archivos JPG. + - Al convertir los escaneos a PDF se eliminan los archivos JPG.
- La lista de escaneos a convertir puede haber cambiado desde - la última vez que se realizó la detección. + - Solo se convertiran los escaneos mostrados en esta lista. +
+ - Si ejecutas la detección de escaneos nuevamente los nombres de + los archivos cambiarán. Esto es normal. El contenido de los JPG + es el mismo.

+ +
+ + + + + + + + + + Convertir escaneos + + +
diff --git a/frontend/src/certGenerator/certs/CertData.ts b/frontend/src/certGenerator/certs/CertData.ts index 3ac911c..8ccfffb 100644 --- a/frontend/src/certGenerator/certs/CertData.ts +++ b/frontend/src/certGenerator/certs/CertData.ts @@ -36,7 +36,7 @@ export type CertData = { */ certDay: string, /** - * Id of the certificate + * Id of the certificate in database (not the same as certCode) */ certIId: number, /** diff --git a/frontend/src/certGenerator/certs/utils.ts b/frontend/src/certGenerator/certs/utils.ts index 875ccbd..ddd7631 100644 --- a/frontend/src/certGenerator/certs/utils.ts +++ b/frontend/src/certGenerator/certs/utils.ts @@ -69,8 +69,7 @@ export async function getQR(data : { verticalOffset: number, behindDocument?: boolean, }): Promise { - // Old URL: https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()} - // New URL: https://eegsac.com/certificado/${dni()} + // https://eegsac.com/certificado/${dni()} const qr = await QR.toDataURL(`https://eegsac.com/certificado/${data.dni}?iid=${data.iid}`, {margin: 1}); return new ImageRun({ diff --git a/frontend/src/icons/PDFIcon.tsx b/frontend/src/icons/PDFIcon.tsx new file mode 100644 index 0000000..424afc4 --- /dev/null +++ b/frontend/src/icons/PDFIcon.tsx @@ -0,0 +1,9 @@ + + +export function PDFIcon(props: {fill: string}) { + return ( + + + + ); +}