[BE] Detect QR, parse params & get data from DB
This commit is contained in:
parent
373ef5c74e
commit
605ee27b4f
@ -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<JsonResult<Vec<(PathBuf, ScanInfo)>
|
||||
(Status::Ok, JsonResult::ok(file_details))
|
||||
}
|
||||
|
||||
#[options("/scans/convert")]
|
||||
pub fn convert_scans_options() -> Status {
|
||||
Status::Ok
|
||||
}
|
||||
|
||||
#[post("/scans/convert", format = "json", data = "<data>")]
|
||||
pub async fn convert_scans(
|
||||
data: Json<JsonResult<Vec<(PathBuf, ScanInfo)>>>,
|
||||
) -> (Status, Json<JsonResult<()>>) {
|
||||
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<Vec<PathBuf>, 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(())
|
||||
}
|
||||
|
@ -72,6 +72,8 @@ async fn rocket() -> _ {
|
||||
// Scans routes
|
||||
//
|
||||
controller::scans::detect_scans,
|
||||
controller::scans::convert_scans_options,
|
||||
controller::scans::convert_scans,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
@ -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<Vec<ScanData>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
<div class="grid grid-cols-[25rem_25rem_auto]">
|
||||
<div>
|
||||
@ -126,6 +159,7 @@ export function Scans() {
|
||||
<div class="relative">
|
||||
<FilledButton
|
||||
onClick={detectScans}
|
||||
disabled={loading()}
|
||||
>
|
||||
<span
|
||||
class="absolute top-[1.35rem] left-2"
|
||||
@ -174,11 +208,43 @@ export function Scans() {
|
||||
</div>
|
||||
|
||||
<p class="p-4">
|
||||
Al convertir los escaneos a PDF, se eliminan los archivos JPG.
|
||||
- Al convertir los escaneos a PDF se eliminan los archivos JPG.
|
||||
<br />
|
||||
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.
|
||||
<br />
|
||||
- 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.
|
||||
</p>
|
||||
|
||||
<div class="relative mx-4 mb-4">
|
||||
<FilledButton
|
||||
onClick={convertScans}
|
||||
disabled={loading()}
|
||||
>
|
||||
<span
|
||||
class="absolute top-[1.35rem] left-2"
|
||||
style={{display: loading() ? "none" : "inline-block"}}
|
||||
>
|
||||
<PDFIcon
|
||||
fill="var(--c-on-primary)"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="absolute top-[1.35rem] left-2"
|
||||
style={{display: loading() ? "inline-block" : "none"}}
|
||||
>
|
||||
<LoadingIcon
|
||||
class="animate-spin"
|
||||
fill="var(--c-primary-container)"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span class="ml-5">
|
||||
Convertir escaneos
|
||||
</span>
|
||||
</FilledButton>
|
||||
</div>
|
||||
</FilledCard>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,7 +36,7 @@ export type CertData<T> = {
|
||||
*/
|
||||
certDay: string,
|
||||
/**
|
||||
* Id of the certificate
|
||||
* Id of the certificate in database (not the same as certCode)
|
||||
*/
|
||||
certIId: number,
|
||||
/**
|
||||
|
@ -69,8 +69,7 @@ export async function getQR(data : {
|
||||
verticalOffset: number,
|
||||
behindDocument?: boolean,
|
||||
}): Promise<typeof ImageRun> {
|
||||
// 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({
|
||||
|
9
frontend/src/icons/PDFIcon.tsx
Normal file
9
frontend/src/icons/PDFIcon.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
export function PDFIcon(props: {fill: string}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill={props.fill} viewBox="0 0 256 256">
|
||||
<path d="M224,152a8,8,0,0,1-8,8H192v16h16a8,8,0,0,1,0,16H192v16a8,8,0,0,1-16,0V152a8,8,0,0,1,8-8h32A8,8,0,0,1,224,152ZM92,172a28,28,0,0,1-28,28H56v8a8,8,0,0,1-16,0V152a8,8,0,0,1,8-8H64A28,28,0,0,1,92,172Zm-16,0a12,12,0,0,0-12-12H56v24h8A12,12,0,0,0,76,172Zm88,8a36,36,0,0,1-36,36H112a8,8,0,0,1-8-8V152a8,8,0,0,1,8-8h16A36,36,0,0,1,164,180Zm-16,0a20,20,0,0,0-20-20h-8v40h8A20,20,0,0,0,148,180ZM40,112V40A16,16,0,0,1,56,24h96a8,8,0,0,1,5.66,2.34l56,56A8,8,0,0,1,216,88v24a8,8,0,0,1-16,0V96H152a8,8,0,0,1-8-8V40H56v72a8,8,0,0,1-16,0ZM160,80h28.69L160,51.31Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user