/* eslint no-console: "off" */ const path = require('path'); const fs = require('fs-extra'); module.exports = Self => { Self.remoteMethodCtx('updateData', { description: 'Updates schema data from external provider', accessType: 'WRITE', returns: { type: 'object', root: true }, http: { path: `/updateData`, verb: 'POST' } }); Self.updateData = async ctx => { const models = Self.app.models; // Get files checksum const tx = await Self.beginTransaction({}); try { const options = {transaction: tx, userId: ctx.req.accessToken.userId}; const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options); const updatableFiles = []; for (const file of files) { const fileChecksum = await getChecksum(file); if (file.checksum != fileChecksum) { updatableFiles.push({ name: file.name, checksum: fileChecksum }); } else console.debug(`File already updated, skipping...`); } if (updatableFiles.length === 0) return false; // Download files const container = await models.TempContainer.container('edi'); const tempPath = path.join(container.client.root, container.name); let remoteFile; let tempDir; let tempFile; const fileNames = updatableFiles.map(file => file.name); const tables = await Self.rawSql(` SELECT fileName, toTable, file FROM edi.tableConfig WHERE file IN (?)`, [fileNames], options); for (const table of tables) { const fileName = table.file; remoteFile = `codes/${fileName}.ZIP`; tempDir = `${tempPath}/${fileName}`; tempFile = `${tempPath}/${fileName}.zip`; try { await fs.readFile(tempFile); } catch (error) { if (error.code === 'ENOENT') { console.debug(`Downloading file ${fileName}...`); const downloadOutput = await downloadFile(remoteFile, tempFile); if (downloadOutput.error) continue; } } await extractFile(fileName, tempFile, tempDir); console.debug(`Updating table ${table.toTable}...`); await dumpData(tempDir, table, options); } // Update files checksum for (const file of updatableFiles) { console.log(`Updating file ${file.name} checksum...`); await Self.rawSql(` UPDATE edi.fileConfig SET checksum = ? WHERE name = ?`, [file.checksum, file.name], options); } await tx.commit(); // Clean files try { console.debug(`Cleaning files...`); await fs.remove(tempPath); } catch (error) { if (error.code !== 'ENOENT') throw e; } return true; } catch (error) { await tx.rollback(); throw error; } }; let ftpClient; async function getFtpClient() { if (!ftpClient) { const [ftpConfig] = await Self.rawSql('SELECT host, user, password FROM edi.ftpConfig'); console.debug(`Openning FTP connection to ${ftpConfig.host}...\n`); const FtpClient = require('ftps'); ftpClient = new FtpClient({ host: ftpConfig.host, username: ftpConfig.user, password: ftpConfig.password, procotol: 'ftp', additionalLftpCommands: 'set ssl:verify-certificate no' }); } return ftpClient; } async function getChecksum(file) { const ftpClient = await getFtpClient(); console.debug(`Checking checksum for file ${file.name}...`); ftpClient.cat(`codes/${file.name}.TXT`); const response = await new Promise((resolve, reject) => { ftpClient.exec((err, response) => { if (err || response.error) { console.debug(`Error downloading checksum file... ${response.error}`); return reject(err); } resolve(response); }); }); if (response && response.data) { const fileContents = response.data; const rows = fileContents.split('\n'); const row = rows[4]; const columns = row.split(/\s+/); let fileChecksum; if (file.keyValue) fileChecksum = columns[1]; if (!file.keyValue) fileChecksum = columns[0]; return fileChecksum; } } async function downloadFile(remoteFile, tempFile) { const ftpClient = await getFtpClient(); ftpClient.get(remoteFile, tempFile); return new Promise((resolve, reject) => { ftpClient.exec((err, response) => { if (err || response.error) { console.debug(`Error downloading file... ${response.error}`); return reject(err); } resolve(response); }); }); } async function extractFile(fileName, tempFile, tempDir) { const JSZip = require('jszip'); try { await fs.mkdir(tempDir); console.debug(`Extracting file ${fileName}...`); } catch (error) { if (error.code !== 'EEXIST') throw e; } const fileStream = await fs.readFile(tempFile); if (fileStream) { const zip = new JSZip(); const zipContents = await zip.loadAsync(fileStream); if (!zipContents) return; const fileNames = Object.keys(zipContents.files); for (const fileName of fileNames) { const fileContent = await zip.file(fileName).async('nodebuffer'); const dest = path.join(tempDir, fileName); await fs.writeFile(dest, fileContent); } } } async function dumpData(tempDir, table, options) { const toTable = table.toTable; const baseName = table.fileName; console.log(`Emptying table ${toTable}...`); const tableName = `edi.${toTable}`; await Self.rawSql(`DELETE FROM ??`, [tableName]); const dirFiles = await fs.readdir(tempDir); const files = dirFiles.filter(file => file.startsWith(baseName)); for (const file of files) { console.log(`Dumping data from file ${file}...`); const templatePath = path.join(__dirname, `./sql/${toTable}.sql`); const sqlTemplate = await fs.readFile(templatePath, 'utf8'); const filePath = path.join(tempDir, file); await Self.rawSql(sqlTemplate, [filePath], options); await Self.rawSql(` UPDATE edi.tableConfig SET updated = ? WHERE fileName = ? `, [Date.vnNew(), baseName], options); } console.log(`Updated table ${toTable}\n`); } };