const axios = require('axios'); const {DOMParser} = require('xmldom'); const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethod('exchangeRateUpdate', { description: 'Updates the exchange rates from an XML feed', accessType: 'WRITE', accepts: [], http: { path: '/exchangeRateUpdate', verb: 'post' } }); Self.exchangeRateUpdate = async(options = {}) => { const models = Self.app.models; const myOptions = {}; Object.assign(myOptions, options); let createdTx = false; if (!myOptions.transaction) { myOptions.transaction = await Self.beginTransaction({}); createdTx = true; } try { const response = await axios.get('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml'); const xmlData = response.data; const doc = new DOMParser({errorHandler: {warning: () => {}}}) .parseFromString(xmlData, 'text/xml'); const cubes = doc?.getElementsByTagName('Cube'); if (!cubes || cubes.length === 0) throw new UserError('No cubes found. Exiting the method.'); const maxDateRecord = await models.ReferenceRate.findOne({order: 'dated DESC'}, myOptions); const maxDate = maxDateRecord?.dated ? new Date(maxDateRecord.dated) : null; let lastProcessedDate = maxDate; for (const cube of Array.from(cubes)) { if (cube.nodeType === doc.ELEMENT_NODE && cube.attributes.getNamedItem('time')) { const xmlDate = new Date(cube.getAttribute('time')); const xmlDateWithoutTime = new Date( xmlDate.getFullYear(), xmlDate.getMonth(), xmlDate.getDate() ); if (!maxDate || xmlDateWithoutTime > maxDate) { if (lastProcessedDate && xmlDateWithoutTime > lastProcessedDate) { for (const code of ['USD', 'CNY', 'GBP']) { const currency = await models.Currency.findOne( {where: {code}}, myOptions ); if (!currency) throw new UserError(`Currency not found for code: ${code}`); await fillMissingDates( models, currency, lastProcessedDate, xmlDateWithoutTime, myOptions ); } } } for (const rateCube of Array.from(cube.childNodes)) { if (rateCube.nodeType === doc.ELEMENT_NODE) { const currencyCode = rateCube.getAttribute('currency'); const rate = rateCube.getAttribute('rate'); if (['USD', 'CNY', 'GBP'].includes(currencyCode)) { const currency = await models.Currency.findOne( {where: {code: currencyCode}}, myOptions ); if (!currency) throw new UserError(`Currency not found for code: ${currencyCode}`); const existingRate = await models.ReferenceRate.findOne({ where: {currencyFk: currency.id, dated: xmlDateWithoutTime} }, myOptions); if (existingRate) { if (existingRate.value !== rate) await existingRate.updateAttributes({value: rate}, myOptions); } else { await models.ReferenceRate.create({ currencyFk: currency.id, dated: xmlDateWithoutTime, value: rate }, myOptions); } } } } lastProcessedDate = xmlDateWithoutTime; } } if (createdTx) await myOptions.transaction.commit(); } catch (error) { if (createdTx) await myOptions.transaction.rollback(); throw error; } }; async function getLastValidRate(models, currencyId, date, myOptions) { return models.ReferenceRate.findOne({ where: {currencyFk: currencyId, dated: {lt: date}}, order: 'dated DESC' }, myOptions); } async function fillMissingDates(models, currency, startDate, endDate, myOptions) { const cursor = new Date(startDate); cursor.setDate(cursor.getDate() + 1); while (cursor < endDate) { const existingRate = await models.ReferenceRate.findOne({ where: {currencyFk: currency.id, dated: cursor} }, myOptions); if (!existingRate) { const lastValid = await getLastValidRate(models, currency.id, cursor, myOptions); if (lastValid) { await models.ReferenceRate.create({ currencyFk: currency.id, dated: new Date(cursor), value: lastValid.value }, myOptions); } } cursor.setDate(cursor.getDate() + 1); } } };