Skip to content

Commit

Permalink
Add document sha256 (project-openubl#313)
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosthe19916 authored Jan 6, 2024
1 parent b96a7e7 commit 7370690
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 50 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions openubl/api/src/system/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,23 @@ impl ProjectContext {
Ok(())
}

pub async fn get_document_by_ubl_params(
&self,
ruc: &str,
document_type: &str,
document_id: &str,
sha256: &str,
tx: Transactional<'_>,
) -> Result<Option<ubl_document::Model>, Error> {
Ok(ubl_document::Entity::find()
.filter(entity::ubl_document::Column::Ruc.eq(ruc))
.filter(entity::ubl_document::Column::TipoDocumento.eq(document_type))
.filter(entity::ubl_document::Column::SerieNumero.eq(document_id))
.filter(entity::ubl_document::Column::Sha256.eq(sha256))
.one(&self.system.connection(tx))
.await?)
}

pub async fn create_document(
&self,
model: &ubl_document::Model,
Expand All @@ -126,6 +143,7 @@ impl ProjectContext {
serie_numero: Set(model.serie_numero.clone()),
tipo_documento: Set(model.tipo_documento.clone()),
baja_tipo_documento_codigo: Set(model.baja_tipo_documento_codigo.clone()),
sha256: Set(model.sha256.clone()),
..Default::default()
};

Expand Down
1 change: 1 addition & 0 deletions openubl/entity/src/ubl_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct Model {
pub serie_numero: String,
pub tipo_documento: String,
pub baja_tipo_documento_codigo: Option<String>,
pub sha256: String,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
Expand Down
2 changes: 2 additions & 0 deletions openubl/migration/src/m20240101_104121_create_ubl_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl MigrationTrait for Migration {
.not_null(),
)
.col(ColumnDef::new(UblDocument::BajaTipoDocumentoCodigo).string())
.col(ColumnDef::new(UblDocument::Sha256).string().not_null())
.foreign_key(
ForeignKey::create()
.from_col(UblDocument::ProjectId)
Expand Down Expand Up @@ -57,4 +58,5 @@ enum UblDocument {
SerieNumero,
TipoDocumento,
BajaTipoDocumentoCodigo,
Sha256,
}
103 changes: 65 additions & 38 deletions openubl/server/src/server/files.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use actix_4_jwt_auth::AuthenticatedUser;
use actix_multipart::form::tempfile::TempFile;
use actix_multipart::form::MultipartForm;
use actix_web::http::StatusCode;
use actix_web::{post, web, HttpResponse, Responder};
use anyhow::anyhow;

Expand Down Expand Up @@ -28,51 +29,77 @@ pub async fn upload_file(
) -> Result<impl Responder, Error> {
let project_id = path.into_inner();

match state
let ctx = state
.system
.get_project(project_id, &user.claims.user_id(), Transactional::None)
.await
.map_err(Error::System)?
{
None => Ok(HttpResponse::NotFound().finish()),
Some(ctx) => {
match form.files.first() {
None => Ok(HttpResponse::BadRequest().finish()),
Some(temp_file) => {
// Read file
let ubl_file = UblFile::from_path(temp_file.file.path())?;
let file_metadata = ubl_file.metadata()?;
.ok_or(Error::BadRequest {
status: StatusCode::NOT_FOUND,
msg: "Project not found".to_string(),
})?;

// Upload to storage
let file_path = temp_file.file.path().to_str().ok_or(Error::Any(anyhow!(
"Could not extract the file path of the temp file"
)))?;
let filename = temp_file
.file_name
.clone()
.unwrap_or("file.xml".to_string());
let file_id = state
.storage
.upload(ctx.project.id, file_path, &filename)
.await?;
let temp_file = form.files.first().ok_or(Error::BadRequest {
status: StatusCode::BAD_REQUEST,
msg: "No file found to be processed".to_string(),
})?;

// Create file
let document_model = ubl_document::Model {
id: 0,
project_id: ctx.project.id,
file_id,
ruc: file_metadata.ruc,
serie_numero: file_metadata.document_id,
tipo_documento: file_metadata.document_type,
baja_tipo_documento_codigo: file_metadata.voided_line_document_type_code,
};
// Read file
let ubl_file = UblFile::from_path(temp_file.file.path())?;
let file_metadata = ubl_file.metadata()?;
let file_sha256 = ubl_file.sha256();

let document = ctx
.create_document(&document_model, Transactional::None)
.await?;
Ok(HttpResponse::Created().json(document))
}
}
// Verify file so we don't upload the same file twice
let prev_file = ctx
.get_document_by_ubl_params(
&file_metadata.ruc,
&file_metadata.document_type,
&file_metadata.document_id,
&file_sha256,
Transactional::None,
)
.await?;

match &prev_file {
None => {
// Upload to storage
let file_path = temp_file.file.path().to_str().ok_or(Error::Any(anyhow!(
"Could not extract the file path of the temp file"
)))?;

let file_id = state
.storage
.upload_ubl_xml(
ctx.project.id,
&file_metadata.ruc,
&file_metadata.document_type,
&file_metadata.document_id,
&file_sha256,
file_path,
)
.await?;

// Create file
let document_model = ubl_document::Model {
id: 0,
project_id: ctx.project.id,
file_id,
ruc: file_metadata.ruc,
serie_numero: file_metadata.document_id,
tipo_documento: file_metadata.document_type,
baja_tipo_documento_codigo: file_metadata.voided_line_document_type_code,
sha256: file_sha256,
};

let document = ctx
.create_document(&document_model, Transactional::None)
.await?;

Ok(HttpResponse::Created().json(document))
}
Some(_) => Err(Error::BadRequest {
status: StatusCode::CONFLICT,
msg: "File already uploaded".to_string(),
}),
}
}
6 changes: 6 additions & 0 deletions openubl/server/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::fmt::Display;

use actix_web::body::BoxBody;
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};

use openubl_api::system;
Expand All @@ -19,6 +20,8 @@ pub enum Error {
Io(std::io::Error),
#[error(transparent)]
Storage(#[from] StorageSystemErr),
#[error("Invalid request {msg:?}")]
BadRequest { msg: String, status: StatusCode },
#[error(transparent)]
Any(#[from] anyhow::Error),
}
Expand Down Expand Up @@ -61,6 +64,9 @@ impl ResponseError for Error {
}
Self::Storage(err) => HttpResponse::InternalServerError()
.json(ErrorInformation::new("System storage", err)),
Self::BadRequest { msg, status } => {
HttpResponse::build(*status).json(ErrorInformation::new("Bad request", msg))
}
Self::Any(err) => HttpResponse::InternalServerError()
.json(ErrorInformation::new("System unknown", err)),
}
Expand Down
25 changes: 17 additions & 8 deletions openubl/storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,32 +70,40 @@ impl StorageSystem {
}
}

pub async fn upload(
pub async fn upload_ubl_xml(
&self,
project_id: i32,
file_path: &str,
filename: &str,
ruc: &str,
document_type: &str,
document_id: &str,
file_sha246: &str,
file_full_path: &str,
) -> Result<String, StorageSystemErr> {
let zip_name = format!("{}.zip", Uuid::new_v4());
let zip_path = zip_file(&zip_name, file_path, filename)?;
let file_name_inside_zip = format!("{ruc}-{}.xml", document_id.to_uppercase());
let zip_path = zip_file(file_full_path, &file_name_inside_zip)?;

let short_sha256: String = file_sha246.chars().take(7).collect();
let zip_name = format!("{}_{short_sha256}.zip", document_id.to_uppercase());

match self {
StorageSystem::FileSystem(workspace) => {
let object_name = Path::new(workspace)
.join(project_id.to_string())
.join(ruc)
.join(document_type)
.join(&zip_name);

rename(zip_path, object_name)?;
Ok(zip_name.clone())
}
StorageSystem::Minio(bucket, client) => {
let object_name = format!("{project_id}/{zip_name}");
let object_name = format!("{project_id}/{ruc}/{document_type}/{zip_name}");

let object = &UploadObjectArgs::new(bucket, &object_name, &zip_path)?;
let response = client.upload_object(object).await?;

// Clear temp files
fs::remove_file(file_path)?;
fs::remove_file(file_full_path)?;
fs::remove_file(zip_path)?;

Ok(response.object_name)
Expand All @@ -105,10 +113,11 @@ impl StorageSystem {
}

pub fn zip_file(
zip_filename: &str,
full_path_of_file_to_be_zipped: &str,
file_name_to_be_used_in_zip: &str,
) -> ZipResult<String> {
let zip_filename = format!("{}.zip", Uuid::new_v4());

let mut file = File::open(full_path_of_file_to_be_zipped)?;
let file_path = Path::new(full_path_of_file_to_be_zipped);
let file_directory = file_path.parent().ok_or(ZipError::InvalidArchive(
Expand Down
1 change: 1 addition & 0 deletions xsender/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ regex = "1.10.2"
base64 = "0.21.5"
thiserror = "1.0.53"
anyhow = "1.0.78"
sha2 = "0.10.8"

[dev-dependencies]
serial_test = "2.0.0"
Expand Down
16 changes: 12 additions & 4 deletions xsender/src/ubl_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::fs;
use std::path::Path;

use anyhow::anyhow;
use sha2::{Digest, Sha256};
use xml::name::OwnedName;
use xml::reader::XmlEvent;
use xml::EventReader;
Expand Down Expand Up @@ -138,16 +139,23 @@ impl UblFile {

match (document_type, document_id, ruc) {
(Some(document_type), Some(document_id), Some(ruc)) => Ok(UblMetadata {
document_type,
document_id,
ruc,
voided_line_document_type_code,
document_type: document_type.trim().to_string(),
document_id: document_id.trim().to_string(),
ruc: ruc.trim().to_string(),
voided_line_document_type_code: voided_line_document_type_code
.map(|e| e.trim().to_string()),
}),
_ => Err(anyhow!(
"document_type, document_id, and ruc were not found"
)),
}
}

pub fn sha256(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(self.file_content.as_bytes());
format!("{:x}", hasher.finalize())
}
}

#[cfg(test)]
Expand Down

0 comments on commit 7370690

Please sign in to comment.