[BE][Certs] Connect to DB. Get person from DB or fake SUNAT API
This commit is contained in:
parent
c32e2ccb4a
commit
926bcdda9c
2
backend/.env.example
Normal file
2
backend/.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
DATABASE_URL=mysql://user:password@localhost:3306/database
|
||||||
|
RENIEC_API=B8RT6dKlN5DF408cD5vds
|
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
target
|
target
|
||||||
|
.env
|
||||||
|
4
backend/.vscode/settings.json
vendored
Normal file
4
backend/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.inlayHints.typeHints.enable": false,
|
||||||
|
"rust-analyzer.inlayHints.chainingHints.enable": false
|
||||||
|
}
|
11
backend/Cargo.lock
generated
11
backend/Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
11
backend/sql/schema.sql
Normal 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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
@ -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
28
backend/src/cors.rs
Normal 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"));
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
])
|
],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
64
backend/src/model/reniec_person.rs
Normal file
64
backend/src/model/reniec_person.rs
Normal 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,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user