import crypto from 'crypto' import fs from 'fs' import axios from 'axios' import { DuplicatedFileError, IFileServiceImplementation, NativeFile, PreparedFiles, } from '../../interfaces' import { CONTENT_TYPES, FILES_API } from '../../services' import { ApisauceInstance } from '../../services/api/apisauce' import { FileToSign } from '../../services/edocu/batch' import { DuplicateError, SignError } from '../fileServiceProvider' export default class FileService implements IFileServiceImplementation { api: ApisauceInstance constructor(api: ApisauceInstance) { this.api = api } async uploadFiles( type: string, hash: string, organization: string, files: PreparedFiles[], ) { console.log('0. uploadFiles', files) // 1. SIGN const fileHashes: Record = {} const filesData = await files.reduce( async (accFilesP: Promise, input) => { const accFiles = await accFilesP const inputData = await input.value.reduce( async (accInputP: Promise, file) => { const accInput = await accInputP const fileHash = await this.getFileHash(file.data) fileHashes[fileHash] = file.data.uri const fileAttribute = typeof input.attribute === 'string' ? input.attribute : input.attribute.id accInput.push({ file: { filename: file.data.name, file_size: file.data.size ?? 0, contentType: file.data.mimeType ?? 'text/plain', md5: fileHash, }, attribute: { id: fileAttribute, }, }) return accInput }, Promise.resolve([]), ) return accFiles.concat(inputData) }, Promise.resolve([]), ) console.log('1. FilesData', filesData) const signRequest = await this.api.post< Record, DuplicatedFileError[] >( `${FILES_API}/credentials/${type}/${hash}/put`, { organization: { id: organization }, files: filesData, }, { headers: { 'Content-Type': CONTENT_TYPES.CREDENTIAL, }, }, ) if (!signRequest.ok) { if (signRequest.status === 409) { throw new DuplicateError( 'Duplicated files', signRequest?.originalError.response?.data || [], ) } else { throw new SignError('Cannot sign files') } } if (signRequest.data) { // 2. UPLOAD const uploadPromises = Object.entries(signRequest.data).map( ([fileHash, fileData]) => { console.log( `2. upload file ${fileHash} to ${fileData.url} at path ${fileHashes[fileHash]}`, ) return this.uploadFile(fileData.url, fileHashes[fileHash]) }, ) console.log('2. uploadPromises', uploadPromises) await Promise.all(uploadPromises) } // 3. BATCH const batchData = [ { element: { type, hash }, files: filesData, organization: { id: organization }, }, ] return batchData } uploadFile(url: string, path: string) { const { size } = fs.statSync(path) const requestConfig = { maxContentLength: Infinity, headers: { 'Content-Length': size, 'Content-Type': 'application/json', }, } return axios.put(url, fs.createReadStream(path), requestConfig) } getFileHash(fileData: NativeFile): Promise { return new Promise((res) => { const filePath = fileData.uri const hash = crypto.createHash('md5') const rStream = fs.createReadStream(filePath) rStream.on('data', (data) => { hash.update(data) }) rStream.on('end', () => { res(hash.digest('hex')) }) }) } }