[BE][Certs] Connect to DB. Get person from DB or fake SUNAT API

This commit is contained in:
Araozu 2023-08-29 16:04:47 -05:00
parent c32e2ccb4a
commit 926bcdda9c
15 changed files with 269 additions and 56 deletions

2
backend/.env.example Normal file
View File

@ -0,0 +1,2 @@
DATABASE_URL=mysql://user:password@localhost:3306/database
RENIEC_API=B8RT6dKlN5DF408cD5vds

3
backend/.gitignore vendored
View File

@ -1 +1,2 @@
target target
.env

4
backend/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"rust-analyzer.inlayHints.typeHints.enable": false,
"rust-analyzer.inlayHints.chainingHints.enable": false
}

11
backend/Cargo.lock generated
View File

@ -102,8 +102,11 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
name = "backend" name = "backend"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dotenvy",
"once_cell",
"reqwest", "reqwest",
"rocket", "rocket",
"serde",
"sqlx", "sqlx",
] ]
@ -1719,18 +1722,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.186" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.186" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -6,7 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
reqwest = "0.11.20" reqwest = { version = "0.11", features = ["json"] }
rocket = { version = "=0.5.0-rc.3" , features = ["json", "msgpack", "uuid"] } rocket = { version = "=0.5.0-rc.3" , features = ["json", "msgpack", "uuid"] }
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "tls-rustls" ] } sqlx = { version = "0.7.1", features = [ "runtime-tokio", "tls-rustls", "mysql", "macros" ] }
once_cell = "1.18.0"
dotenvy = "0.15.7"
serde = "1.0.188"

11
backend/sql/schema.sql Normal file
View File

@ -0,0 +1,11 @@
# Mysql schema
CREATE TABLE person (
person_id INTEGER PRIMARY KEY AUTO_INCREMENT,
person_dni VARCHAR(8) NOT NULL,
person_names VARCHAR(50) NOT NULL,
person_paternal_surname VARCHAR(30) NOT NULL,
person_maternal_surname VARCHAR(30) NOT NULL
)

View File

@ -1,15 +1,96 @@
use rocket::http::Status;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use reqwest::Client;
use crate::model::person::Person; use crate::{db, model::person::Person};
use crate::model::reniec_person::ReniecPerson;
#[get("/person/<dni>")] #[get("/person/<dni>")]
pub async fn get_by_dni(dni: i32) -> Json<Person> { pub async fn get_by_dni(dni: i32) -> (Status, Json<Person>) {
// TODO: get from database let db = db();
Json(Person {
person_id: 1, /*
person_dni: format!("{}", dni), * Search person in DB
person_names: "Juan".to_string(), */
person_paternal_surname: "Perez".to_string(), let query = sqlx::query!("SELECT * FROM person WHERE person_dni = ?", dni)
person_maternal_surname: "Gomez".to_string(), .fetch_one(db)
}) .await;
// Return if found
if let Ok(row) = query {
let person = Person {
person_id: row.person_id,
person_dni: row.person_dni,
person_names: row.person_names,
person_paternal_surname: row.person_paternal_surname,
person_maternal_surname: row.person_maternal_surname,
};
return (Status::Ok, Json(person));
}
/*
* Person not found in DB. Search in fake RENIEC API with reqwest
*/
let reniec_api_key = std::env::var("RENIEC_API").expect("RENIEC_API env var is not set!");
let client = Client::new();
let reqwest_result = client
.get(format!("https://api.apis.net.pe/v1/dni?numero={}", &dni))
.bearer_auth(reniec_api_key)
.header("Content-Type", "application/json")
.header("charset", "utf-8")
.send()
.await;
let reniec_person = match reqwest_result {
Ok(data) => {
let person = data.json::<ReniecPerson>().await;
match person {
Ok(p) => Some(p),
Err(reason) => {
eprintln!(
"Error parsing data from fake RENIEC API into ReniecPerson: {:?}",
reason
);
None
}
}
}
Err(error) => {
eprintln!("Error fetching person from fake RENIEC API: {:?}", error);
None
}
};
// If person is found in fake RENIEC API, insert to DB and return
if let Some(p) = reniec_person {
let query = sqlx::query!(
"INSERT INTO person (person_dni, person_names, person_paternal_surname, person_maternal_surname) VALUES (?, ?, ?, ?)",
dni,
p.nombres,
p.apellidoPaterno,
p.apellidoMaterno,
)
.execute(db)
.await;
if let Err(reason) = query {
eprintln!("Error inserting person into DB: {:?}", reason);
return (Status::InternalServerError, Json(Person::default()));
}
let person = Person {
person_id: 0,
person_dni: format!("{}", dni),
person_names: p.nombres,
person_paternal_surname: p.apellidoPaterno,
person_maternal_surname: p.apellidoMaterno,
};
return (Status::Ok, Json(person));
}
// Return error
(Status::NotFound, Json(Person::default()))
} }

View File

@ -1,6 +1,6 @@
use rocket::{serde::json::Json, http::Status}; use rocket::{http::Status, serde::json::Json};
use crate::model::register::{RegisterCreate, Register}; use crate::model::register::{Register, RegisterCreate};
#[options("/register/batch")] #[options("/register/batch")]
pub fn options() -> Status { pub fn options() -> Status {
@ -22,6 +22,7 @@ pub async fn get_by_dni(dni: i32) -> Json<Vec<Register>> {
register_custom_label: "".to_string(), register_custom_label: "".to_string(),
register_person_id: 1, register_person_id: 1,
register_course_id: 1, register_course_id: 1,
register_is_preview: false,
}; };
let r2 = Register { let r2 = Register {
@ -32,6 +33,7 @@ pub async fn get_by_dni(dni: i32) -> Json<Vec<Register>> {
register_custom_label: "".to_string(), register_custom_label: "".to_string(),
register_person_id: 1, register_person_id: 1,
register_course_id: 2, register_course_id: 2,
register_is_preview: false,
}; };
Json(vec![r1, r2]) Json(vec![r1, r2])

28
backend/src/cors.rs Normal file
View File

@ -0,0 +1,28 @@
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::Header;
pub struct Cors;
#[rocket::async_trait]
impl Fairing for Cors {
fn info(&self) -> Info {
Info {
name: "CORS",
kind: Kind::Response,
}
}
#[cfg(debug_assertions)]
async fn on_response<'r>(
&self,
_: &'r rocket::Request<'_>,
response: &mut rocket::Response<'r>,
) {
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
response.set_header(Header::new(
"Access-Control-Allow-Methods",
"POST, GET, OPTIONS",
));
response.set_header(Header::new("Access-Control-Allow-Headers", "Content-Type"));
}
}

View File

@ -1,47 +1,49 @@
use rocket::fairing::{Fairing, Info, Kind}; use cors::Cors;
use rocket::http::Header; use once_cell::sync::OnceCell;
use sqlx::mysql::MySqlPoolOptions;
use sqlx::{MySql, Pool};
use std::env;
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
mod controller; mod controller;
mod cors;
mod model; mod model;
struct Cors; static DB: OnceCell<Pool<MySql>> = OnceCell::new();
#[rocket::async_trait] pub fn db() -> &'static Pool<MySql> {
impl Fairing for Cors { DB.get().expect("DB not initialized")
fn info(&self) -> Info {
Info {
name: "CORS",
kind: Kind::Response,
}
}
#[cfg(debug_assertions)]
async fn on_response<'r>(
&self,
_: &'r rocket::Request<'_>,
response: &mut rocket::Response<'r>,
) {
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
response.set_header(Header::new(
"Access-Control-Allow-Methods",
"POST, GET, OPTIONS",
));
response.set_header(Header::new("Access-Control-Allow-Headers", "Content-Type"));
}
} }
#[launch] #[launch]
fn rocket() -> _ { async fn rocket() -> _ {
rocket::build() dotenvy::dotenv().expect("Failed to load .env file");
.attach(Cors {})
.mount("/api", routes![ /*
Init DB
*/
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
let pool = MySqlPoolOptions::new()
.max_connections(5)
.connect(db_url.as_str())
.await;
match pool {
Ok(pool) => DB.set(pool).expect("Failed to set DB pool"),
Err(e) => println!("Error connecting to DB: {}", e),
}
/* Init Rocket */
rocket::build().attach(Cors {}).mount(
"/api",
routes![
controller::person::get_by_dni, controller::person::get_by_dni,
controller::course::get_all, controller::course::get_all,
controller::register::insert_all, controller::register::insert_all,
controller::register::options, controller::register::options,
controller::register::get_by_dni, controller::register::get_by_dni,
]) ],
)
} }

View File

@ -14,7 +14,7 @@ pub struct Course {
pub course_display_name: String, pub course_display_name: String,
/// Number of days of the course. /// Number of days of the course.
/// Used to calculate how to space the courses /// Used to calculate how to space the courses
/// ///
/// Example: `2` /// Example: `2`
pub course_days_amount: i32, pub course_days_amount: i32,
/// Whether the course name can be extended with a label. /// Whether the course name can be extended with a label.

View File

@ -1,3 +1,4 @@
pub mod course; pub mod course;
pub mod person; pub mod person;
pub mod register; pub mod register;
pub mod reniec_person;

View File

@ -1,7 +1,6 @@
use rocket::serde::Serialize; use serde::Serialize;
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
#[serde(crate = "rocket::serde")]
pub struct Person { pub struct Person {
/// Internal id /// Internal id
pub person_id: i32, pub person_id: i32,
@ -22,3 +21,15 @@ pub struct Person {
/// Example: `Gomez` /// Example: `Gomez`
pub person_maternal_surname: String, pub person_maternal_surname: String,
} }
impl Person {
pub fn default() -> Person {
Person {
person_id: -1,
person_dni: "".to_string(),
person_names: "".to_string(),
person_paternal_surname: "".to_string(),
person_maternal_surname: "".to_string(),
}
}
}

View File

@ -1,4 +1,4 @@
use rocket::serde::{Serialize, Deserialize}; use rocket::serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
@ -8,10 +8,9 @@ pub struct RegisterCreate {
person_id: i32, person_id: i32,
course_id: i32, course_id: i32,
/// YYYY-MM-DD /// YYYY-MM-DD
date: String date: String,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub struct Register { pub struct Register {
@ -29,6 +28,8 @@ pub struct Register {
/// Used in machinery courses, where the course name is the type of machinery, /// Used in machinery courses, where the course name is the type of machinery,
/// and the custom label the manufacturer and series (i.e. `320D CAT`). /// and the custom label the manufacturer and series (i.e. `320D CAT`).
pub register_custom_label: String, pub register_custom_label: String,
/// Whether this register is a preview of the certificate ("sin firmas")
pub register_is_preview: bool,
/// Foreign key to the person table /// Foreign key to the person table
pub register_person_id: i32, pub register_person_id: i32,
/// Foreign key to the course table /// Foreign key to the course table

View File

@ -0,0 +1,64 @@
use serde::{Deserialize, Serialize};
#[allow(non_snake_case)]
#[derive(Deserialize, Serialize, Clone)]
pub struct ReniecPerson {
/// Full name
///
/// Example: `ADRIAN JORGE LOPEZ APAZA`
pub nombre: String,
/*
/// ?
tipoDocumento: i32,
/// DNI number
///
/// Example: `12345678`
numeroDocumento: String,
/// ?
estado: String,
/// ?
condicion: String,
/// ?
direccion: String,
/// ?
ubigeo: String,
/// ?
viaTipo: String,
/// ?
viaNombre: String,
/// ?
zonaCodigo: String,
/// ?
zonaTipo: String,
/// ?
numero: String,
/// ?
interior: String,
/// ?
lote: String,
/// ?
dpto: String,
/// ?
manzana: String,
/// ?
kilometro: String,
/// ?
distrito: String,
/// ?
provincia: String,
/// ?
departamento: String,
/// First last name.
///
*/
/// Example: `LOPEZ`
pub apellidoPaterno: String,
/// Second last name.
///
/// Example: `APAZA`
pub apellidoMaterno: String,
/// All first names.
///
/// Example: `ADRIAN JORGE`
pub nombres: String,
}