[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
|
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -1 +1,2 @@
|
||||
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"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"once_cell",
|
||||
"reqwest",
|
||||
"rocket",
|
||||
"serde",
|
||||
"sqlx",
|
||||
]
|
||||
|
||||
@ -1719,18 +1722,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.186"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.186"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -6,7 +6,9 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
reqwest = "0.11.20"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
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 reqwest::Client;
|
||||
|
||||
use crate::model::person::Person;
|
||||
use crate::{db, model::person::Person};
|
||||
use crate::model::reniec_person::ReniecPerson;
|
||||
|
||||
#[get("/person/<dni>")]
|
||||
pub async fn get_by_dni(dni: i32) -> Json<Person> {
|
||||
// TODO: get from database
|
||||
Json(Person {
|
||||
person_id: 1,
|
||||
pub async fn get_by_dni(dni: i32) -> (Status, Json<Person>) {
|
||||
let db = db();
|
||||
|
||||
/*
|
||||
* Search person in DB
|
||||
*/
|
||||
let query = sqlx::query!("SELECT * FROM person WHERE person_dni = ?", dni)
|
||||
.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: "Juan".to_string(),
|
||||
person_paternal_surname: "Perez".to_string(),
|
||||
person_maternal_surname: "Gomez".to_string(),
|
||||
})
|
||||
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")]
|
||||
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_person_id: 1,
|
||||
register_course_id: 1,
|
||||
register_is_preview: false,
|
||||
};
|
||||
|
||||
let r2 = Register {
|
||||
@ -32,6 +33,7 @@ pub async fn get_by_dni(dni: i32) -> Json<Vec<Register>> {
|
||||
register_custom_label: "".to_string(),
|
||||
register_person_id: 1,
|
||||
register_course_id: 2,
|
||||
register_is_preview: false,
|
||||
};
|
||||
|
||||
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 rocket::http::Header;
|
||||
use cors::Cors;
|
||||
use once_cell::sync::OnceCell;
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use sqlx::{MySql, Pool};
|
||||
use std::env;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
mod controller;
|
||||
mod cors;
|
||||
mod model;
|
||||
|
||||
struct Cors;
|
||||
static DB: OnceCell<Pool<MySql>> = OnceCell::new();
|
||||
|
||||
#[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"));
|
||||
}
|
||||
pub fn db() -> &'static Pool<MySql> {
|
||||
DB.get().expect("DB not initialized")
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.attach(Cors {})
|
||||
.mount("/api", routes![
|
||||
async fn rocket() -> _ {
|
||||
dotenvy::dotenv().expect("Failed to load .env file");
|
||||
|
||||
/*
|
||||
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::course::get_all,
|
||||
controller::register::insert_all,
|
||||
controller::register::options,
|
||||
controller::register::get_by_dni,
|
||||
])
|
||||
],
|
||||
)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod course;
|
||||
pub mod person;
|
||||
pub mod register;
|
||||
pub mod reniec_person;
|
||||
|
@ -1,7 +1,6 @@
|
||||
use rocket::serde::Serialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Person {
|
||||
/// Internal id
|
||||
pub person_id: i32,
|
||||
@ -22,3 +21,15 @@ pub struct Person {
|
||||
/// Example: `Gomez`
|
||||
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)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
@ -8,10 +8,9 @@ pub struct RegisterCreate {
|
||||
person_id: i32,
|
||||
course_id: i32,
|
||||
/// YYYY-MM-DD
|
||||
date: String
|
||||
date: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Register {
|
||||
@ -29,6 +28,8 @@ pub struct Register {
|
||||
/// 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`).
|
||||
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
|
||||
pub register_person_id: i32,
|
||||
/// 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