[BE][Certs] Batch create registers in a single SQL query
This commit is contained in:
parent
ffb7518ef6
commit
ecdf900299
@ -28,6 +28,21 @@ Si se quiere lanzar mediante Jenkins ya hay un `Jenkinsfile` una carpeta arriba.
|
||||
Modificar lo que sea necesario para ejecutar en el entorno objetivo.
|
||||
|
||||
|
||||
## Base de datos
|
||||
|
||||
**IMPORTANTE**: Este sistema (backend y frontend) depende de que la base de datos tenga ciertos
|
||||
registros con ciertos ids, estos valores estan escritos en el código (hard coded)
|
||||
|
||||
- La tabla `custom_label` debe tener una fila con id: `0` y valor `<vacio>`
|
||||
- La tabla `course` debe tener filas para los cursos matpel:
|
||||
- Matpel 1 con id `10`
|
||||
- Matpel 2 con id `11`
|
||||
- Matpel 3 con id `12`
|
||||
|
||||
Estos valores deben estar en la base de datos. Si se cambiaron, es necesario
|
||||
cambiarlos en el código (sería bueno sacarlos de .env).
|
||||
|
||||
|
||||
## Funcionamiento
|
||||
|
||||
Básicamente, al iniciar el backend realiza lo siguiente:
|
||||
|
@ -17,6 +17,8 @@ pub fn options_delete(_r: i32) -> Status {
|
||||
|
||||
#[post("/register/batch", format = "json", data = "<data>")]
|
||||
pub async fn insert_all(data: Json<Vec<RegisterCreate>>) -> (Status, Json<JsonResult<()>>) {
|
||||
// Old way, inserts one by one
|
||||
/*
|
||||
for register_create in data.iter() {
|
||||
let res = register_create.create().await;
|
||||
|
||||
@ -30,6 +32,18 @@ pub async fn insert_all(data: Json<Vec<RegisterCreate>>) -> (Status, Json<JsonRe
|
||||
}
|
||||
|
||||
(Status::Ok, JsonResult::ok(()))
|
||||
*/
|
||||
|
||||
match RegisterCreate::batch_create(data.into_inner()).await {
|
||||
Ok(_) => (Status::Ok, JsonResult::ok(())),
|
||||
Err(err) => {
|
||||
eprintln!("Error creating register: {:?}", err);
|
||||
(
|
||||
Status::InternalServerError,
|
||||
JsonResult::err(format!("Error creando registros: {}", err)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/register/<dni>")]
|
||||
|
@ -237,13 +237,13 @@ fn get_image_info(path: PathBuf) -> ScanInfo {
|
||||
// Here, an iid is found. Extract it & return
|
||||
|
||||
let dni_length = p - 31;
|
||||
let equals_pos = match url.find('=') {
|
||||
let equals_pos =
|
||||
match url.find('=') {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
return ScanInfo::Error(
|
||||
"QR invalido: La URL tiene signo de interrogacion (?) pero no igual (=).".into(),
|
||||
)
|
||||
}
|
||||
None => return ScanInfo::Error(
|
||||
"QR invalido: La URL tiene signo de interrogacion (?) pero no igual (=)."
|
||||
.into(),
|
||||
),
|
||||
};
|
||||
|
||||
let dni = url.chars().skip(31).take(dni_length).collect::<String>();
|
||||
|
@ -1,9 +1,7 @@
|
||||
use cors::Cors;
|
||||
use once_cell::sync::OnceCell;
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use sqlx::{MySql, Pool, MySqlConnection};
|
||||
use std::env;
|
||||
use sqlx::Connection;
|
||||
use sqlx::MySqlConnection;
|
||||
use std::env;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
@ -35,7 +35,9 @@ impl Course {
|
||||
}
|
||||
};
|
||||
|
||||
let results = sqlx::query!("SELECT * FROM course").fetch_all(&mut db).await;
|
||||
let results = sqlx::query!("SELECT * FROM course")
|
||||
.fetch_all(&mut db)
|
||||
.await;
|
||||
|
||||
let results = match results {
|
||||
Ok(res) => res,
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use rocket::form::validate::Contains;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Executor, Row};
|
||||
@ -138,6 +140,165 @@ impl RegisterCreate {
|
||||
|
||||
Ok(new_max)
|
||||
}
|
||||
|
||||
pub async fn batch_create(registers: Vec<RegisterCreate>) -> Result<(), String> {
|
||||
// Collect all the courses ids in a set
|
||||
let mut course_ids = std::collections::HashSet::<i32>::new();
|
||||
for register in registers.iter() {
|
||||
let course_id = register.course_id;
|
||||
|
||||
// TODO: Move hardcoded values to .env
|
||||
// If the course id is 10, 11 or 12 (matpel ids), add all of them to the set
|
||||
if course_id == 10 || course_id == 11 || course_id == 12 {
|
||||
course_ids.insert(10);
|
||||
course_ids.insert(11);
|
||||
course_ids.insert(12);
|
||||
} else {
|
||||
course_ids.insert(course_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Get next values from all courses ids
|
||||
// This is a map of course_id -> next_register_code,
|
||||
// and will be updated for every register to insert
|
||||
let mut current_register_codes = Self::get_next_register_codes(course_ids).await?;
|
||||
|
||||
// If there's matpel courses (ids 10, 11 & 12), use only id 10, and set it to the highest value
|
||||
if current_register_codes.contains_key(&10) {
|
||||
// Get the highest value
|
||||
let max = current_register_codes
|
||||
.iter()
|
||||
.filter(|(id, _)| *id == &10 || *id == &11 || *id == &12)
|
||||
.map(|(_, value)| value)
|
||||
.max()
|
||||
.unwrap_or(&50);
|
||||
|
||||
current_register_codes.insert(10, *max);
|
||||
}
|
||||
|
||||
// Create values for each register
|
||||
let mut values = Vec::<String>::new();
|
||||
for register in registers.iter() {
|
||||
let course_id = register.course_id;
|
||||
|
||||
// This is the key in the hash map for the current course.
|
||||
let course_code_key = if course_id == 10 || course_id == 11 || course_id == 12 {
|
||||
// As per above, matpel courses share codes. Use the value at 10
|
||||
10
|
||||
} else {
|
||||
course_id
|
||||
};
|
||||
|
||||
// Get the next register code for this course
|
||||
let next_register_code = *current_register_codes.get_mut(&course_code_key).unwrap() + 1;
|
||||
|
||||
// Get the custom label id
|
||||
// This still requires several trips to the database, but:
|
||||
// - Only if there's a custom label
|
||||
// - Custom labels are used only in machineries certs
|
||||
// - Usually there's between 1-2 machineries per request (I personally have never seen more than 3)
|
||||
let custom_label_id = {
|
||||
if register.custom_label.is_empty() {
|
||||
1
|
||||
} else {
|
||||
// Get custom_label_id from db based of self.custom_label
|
||||
let id = CustomLabel::get_id_by_value(®ister.custom_label).await?;
|
||||
|
||||
if id > 0 {
|
||||
id
|
||||
} else {
|
||||
CustomLabel::create(®ister.custom_label).await?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Current date in YYYY-MM-DD format
|
||||
let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();
|
||||
|
||||
// Create the value string
|
||||
let value = format!(
|
||||
"({}, '{}', '{}', {}, {}, {}, {})",
|
||||
next_register_code,
|
||||
current_date,
|
||||
register.date,
|
||||
custom_label_id,
|
||||
register.is_preview,
|
||||
register.person_id,
|
||||
register.course_id
|
||||
);
|
||||
|
||||
// Add it to the list
|
||||
values.push(value);
|
||||
|
||||
// Update the current register code
|
||||
current_register_codes.insert(course_code_key, next_register_code);
|
||||
}
|
||||
|
||||
// Insert all in a single statement
|
||||
let sql = format!(
|
||||
"INSERT INTO register (
|
||||
register_code,
|
||||
register_creation_date,
|
||||
register_display_date,
|
||||
register_custom_label,
|
||||
register_is_preview,
|
||||
register_person_id,
|
||||
register_course_id
|
||||
) VALUES {}",
|
||||
values.join(", ")
|
||||
);
|
||||
|
||||
let mut db = db().await?;
|
||||
match db.fetch_all(sql.as_str()).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
log::error!("Error fetching course & person: {:?}", err);
|
||||
return Err("Error inserting multiple registers".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_next_register_codes(
|
||||
course_ids: HashSet<i32>,
|
||||
) -> Result<HashMap<i32, i32>, String> {
|
||||
let mut db = db().await?;
|
||||
|
||||
let sql = format!(
|
||||
"SELECT MAX(register_code) AS max, register_course_id
|
||||
FROM register
|
||||
WHERE register_course_id IN ({})
|
||||
GROUP BY register_course_id",
|
||||
course_ids
|
||||
.iter()
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
);
|
||||
|
||||
log::info!("SQL: {}", sql);
|
||||
|
||||
let result = match db.fetch_all(sql.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
log::error!("Error fetching max register codes: {:?}", err);
|
||||
return Err("Error fetching max register codes".into());
|
||||
}
|
||||
};
|
||||
|
||||
// Convert to a hashmap
|
||||
let data = result
|
||||
.iter()
|
||||
.map(|row| {
|
||||
(
|
||||
row.try_get("register_course_id").unwrap(),
|
||||
// TODO: This 50 (the minimum value for new registers) should be defined in .env
|
||||
row.try_get("max").unwrap_or(50),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
|
@ -18,11 +18,14 @@ function wait(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// TODO: Refactor
|
||||
export function RegisterPreview(props: {selections: Array<RegistrationPreview>, personId: number | null, onDelete: (v: number) => void, onRegister: () => void}) {
|
||||
const [error, setError] = createSignal("");
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
|
||||
const submit = async() => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
|
||||
await wait(2000);
|
||||
|
||||
@ -41,11 +44,12 @@ export function RegisterPreview(props: {selections: Array<RegistrationPreview>,
|
||||
console.log("Create register: success");
|
||||
// Custom labels may have changed, reload them
|
||||
loadCustomLabels();
|
||||
props.onRegister();
|
||||
} else {
|
||||
console.log(`error. ${result}`);
|
||||
setError(result);
|
||||
}
|
||||
|
||||
props.onRegister();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@ -85,6 +89,12 @@ export function RegisterPreview(props: {selections: Array<RegistrationPreview>,
|
||||
Registrar {props.selections.length} cursos
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<Show when={error() !== ""}>
|
||||
<p class="bg-c-on-error text-c-error p-2 rounded-md">
|
||||
{error()}
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
</FilledCard>
|
||||
);
|
||||
@ -140,6 +150,6 @@ async function createRegisters(data: RegisterBatchCreate): Promise<null | string
|
||||
return null;
|
||||
} else {
|
||||
const data = await response.json();
|
||||
return JSON.stringify(data);
|
||||
return data.Error.reason;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user