Summary Table
Categories |
Total Count |
PII |
0 |
URL |
1 |
DNS |
1 |
EKL |
0 |
IP |
0 |
PORT |
1 |
VsID |
0 |
CF |
0 |
AI |
0 |
VPD |
0 |
PL |
0 |
Other |
0 |
File Content
const express = require('express');
const router = express.Router();
const rest = require('../../rest/rest');
const authMiddleware = require('../../auth/authGuardMiddleware');
const path = require('path');
const fs = require('fs');
const LOGDBG = false; // log detailed debug messages
function logdbg(...args) {
LOGDBG && console.log('Debug ', ...args);
}
const LOGINF = false; // log interesting information messages
function loginf(...args) {
LOGINF && console.log('Info ', ...args);
}
const LOGERR = true; // log critical error messages
function logerr(...args) {
LOGERR && console.log('Error ', ...args);
}
// DRY - Don't Repeat Yourself
// The server https://localhost:4200/ recognizes /assets, but fs.writeFile() does not.
const xmlName = 'XML.XML'; // constant, for now
// const xmlName = 'XMLtest.xml'; // constant, for now --
// const xmlPath = '/assets/xml/'; // failed!
// const xmlPath = path.join(__dirname, '../../../../attachmentViewerTemp');
const appPath = path.join(__dirname, '../../../..'); // where the ars-app is, relative to this file
// Don't use 'src/assets/xml', since it will trigger webpack to re-compile!
// const xmlPath = path.join(appPath, 'src/assets/xml'); // seems to be writable. That might not be good. But try it
const xmlPath = path.join(appPath, 'attachmentViewerTemp');
const xmlFile = path.join(xmlPath, xmlName);
logdbg('xmlFile=', xmlFile);
const hrefCDAView = '/attachment/cda'; // href="cda" in /attachment/xml buffer, is /attachment/cda route
const hrefCDAExport = '../CDA.xsl'; // for export
function xmlCDA(body, cdaUrl) {
// Replace <?xml-stylesheet ?> with local version
// This overrides similar code in the 275parser!!
//
// const cdaUrl = 'cda'; // route, /attachment/cda, not a file
let xml = new String(body);
logdbg(`href='${cdaUrl}'`);
// use pattern for only one replace
// Probaly should de-quote cdaUrl
// xml = xml.replace(/(<\?xml-stylesheet .*)href=['"]([-a-zA-Z0-9.\/\\:]*[\/\\]|)CDA.xsl['"]/, '$1href="' + cdaUrl + '"');
xml = xml.replace(/href=['"]([-a-zA-Z0-9.\/\\:]+)CDA\.xsl['"]/, 'href="' + cdaUrl + '"');
logdbg(xml);
return xml;
}
// I tried many methods of getting the file name from the header and failed many times.
// So, I have abstracted that operation out, to make sure that it is done consistently in one place.
function hdrGetName(headers) {
// Error in /viewAttachment TypeError: headers.get is not a function
// const getXAttachmentName = headers.get('X-Attachment-Name');
// logdbg('getXAttachmentName=', getXAttachmentName); // Doesn't work! node makes it lowercase!!!
// const XAttachmentName = headers['X-Attachment-Name'];
// logdbg('XAttachmentName=', XAttachmentName); // Doesn't work! node makes it lowercase!!!
const xAttachmentName = headers['x-attachment-name'];
loginf('xAttachmentName=', xAttachmentName);
return xAttachmentName;
}
// likewise, pass the file name as a header in our result,
// It could be a mapping, but here, it is the identity.
function resSetName(res, name) {
loginf('resSetName: X-Attachment-Name=', name);
res.set('X-Attachment-Name', name); // pass it along, Upper case gets changed to lowercase at some point!!
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
// content-disposition should be 'inline; filename=foo' or 'attachment; filename=bar'
// Normally inline is shown by the browser, and attachments are saved to a file.
// inline might not have a filename.
// filename is only a default for the SaveAs... dialog.
//
DNS.URL:PORT
// However, /ars/api/v1/file normally just returns
// the filename as the 'Content-Disposition'
// ******** Changed, now
// logdbg(`res.set({'Content-Disposition': ${_contentDisposition || 'falsy _contentDisposition'}});`);
// res.set({'Content-Disposition': _contentDisposition || 'falsy _contentDisposition'});
// res.set('content-disposition', fileName || 'somethingFishy.gif');
// /attachment/searchAttachments included to match plausible searches.
router.post('/searchAttachments', authMiddleware, (req, res) => {
rest
.postResource(
process.env.END_POINT_BASE_URI + process.env.SEARCH_ATTACHMENTS,
req.body
)
.then(data => {
res.status(data.response.statusCode).json(data.responseBody);
})
.catch(error => logerr('/searchAttachments', error));
});
// /attachment/unmatched837Claim
router.post('/unmatched837Claim', authMiddleware, (req, res) => {
rest.postResource(
`${process.env.END_POINT_BASE_URI}${process.env.MANUAL_MATCHING_UNMATCHED_837}`, req.body)
.then(data => {
res.status(data.response.statusCode).json(data.responseBody);
})
.catch(error => logerr('275_attachment.js unmatched837Claim', error));
})
// /attachment/archiveAttachment
router.post('/archiveAttachment', authMiddleware, (req, res) => {
logdbg('/archiveAttachment');
rest.postResource(
`${process.env.END_POINT_BASE_URI}${process.env.ARCHIVE_ATTACHMENT}`, req.body)
.then(data => {
res.status(data.response.statusCode).json(data.responseBody);
})
.catch(error => {
logerr('275_attachment.js /archiveAttachment ', error);
res.status(400).json(error);
})
})
// /attachment/viewAttachment
router.post('/viewAttachment', (req, res) => {
logdbg('/viewAttachment');
const attachmentRequest = req.body;
logdbg('attachmentRequest ', attachmentRequest);
rest.postResource(`${process.env.END_POINT_BASE_URI}/ars/api/v1/file`, attachmentRequest)
.then(data => {
const name = hdrGetName(data.response.headers);
if (data.response.statusCode == 404) {
// not found, occurs on local when attachments are missing.
// presumably could occur on production servers.
// I'm not sure this is the normal way of handling this. PEER REVIEW
loginf('data.response.statusCode=', data.response.statusCode);
resSetName(res, name); // just for informational purposes
return res.status(data.response.statusCode); // Not found happens with local, incomplete attachement db
}
// const _ContentType = data.response.headers['Content-Type']; // undefined
// logdbg('_ContentType=', _ContentType);
let _contentType = data.response.headers['content-type'];
logdbg('_contentType=', _contentType);
_contentType = _contentType || '/'; // if file not found.
if (/^ *application\/json.*/.test(_contentType)) {
// an error?
logdbg('data.response.headers=', JSON.stringify(data.response.headers));
// logdbg('data.responseBody=', data.responseBody);
// logdbg('data=', data); // verbose
}
// Because this is .js, not .ts, we don't have typing on the data value
// we rely on conventions set down
// between Frontend 275Attachment.js and Backend FileOperations.java
// Namely, we expect data to be {response: any, responseBody: any}
// // Despite the fact that FileOperations.java has:
// // .header('Content-Disposition', FilenameUtils.getName(fullPath))
// // The following doesn't work:
// const _ContentDisposition = data.response.headers['Content-Disposition'];
// logdbg('_ContentDisposition=', _ContentDisposition);
// // and this does, because it is lowercase:
// const _contentDisposition = data.response.headers['content-disposition'];
// logdbg('_contentDisposition=', _contentDisposition);
resSetName(res, name); // pass it along
const fileName = name;
if (!fileName) {
logdbg('Error: missing filename');
return res.status(404);
}
let fileBuf = null;
const EXT = fileName.split('.').pop().toUpperCase();
if (EXT === 'XML') {
// XML is copied to a temp file!
// It could have been a Blob (or LocalStorage?)
// This only works because attachment-viewer.html is looking for _THIS_ file.
// How will this work with multiple clients? Is it a local file?
fileBuf = new Buffer('file sent to ' + xmlFile, 'utf8');
// Replace any CDA.xsl in XML
// So that we can use our own cdaUrl
res.contentType('application/octet-stream'); // XML response isn't a file at all, it's a message
fs.writeFile(xmlFile, xmlCDA(data.responseBody, hrefCDAView), err => {
// async call-back
// Error: Can't set headers after they are sent.
fileBuf = new Buffer('xmlFile=' + xmlFile +
(err ? err.errno + ' ' + err.message : 'err is falsy!'),
'utf8'
);
if (err) {
logerr(`Error: writeFile(${xmlFile})`, err);
logerr('Error: ' + err.errno + ' ' + err.message, err);
// throw err;
} else {
logdbg('Success! wrote ', xmlFile);
}
});
} else {
// // Normal file already has contents in data.responseBody
// if (data.responseBody['type'] !== 'Buffer') {
// console.log("data.responseBody['type']=", data.responseBody['type']);
// logerr('typeof data.responseBody=',typeof data.responseBody);
// console.log(`data.responseBody=${JSON.stringify(data.responseBody)}`);
// fileBuf = new Buffer('FAKE!', 'utf8');
// } else {
fileBuf = new Buffer(data.responseBody, 'utf8');
// }
res.contentType(_contentType); // normal file has same type as was sent
}
// res.setHeader('x-attachment-name', data.response.headers['x-attachment-name']);
return res.status(data.response.statusCode).send(fileBuf);
})
.catch(error => logerr('in /viewAttachment ', error));
//const filePath = path.join(__dirname, '../../../../src/assets/images/asset1.png')
//const filePath = path.join(__dirname, '../../../../src/assets/images/attachmentViewerTest.txt')
// const fileStream = fs.createReadStream(filePath);
// fileStream.pipe(res);
});
// /attachment/exportAttachment
router.post('/exportAttachment', (req, res) => {
const attachmentRequest = req.body;
rest.postResource(`${process.env.END_POINT_BASE_URI}/ars/api/v1/file`, attachmentRequest)
.then(data => {
// res.setEncoding('binary')
// res.contentType('application/octet-stream')
// logdbg(data.response)
const _contentType = data.response.headers['content-type'];
logdbg('_contentType=', _contentType);
res.contentType(_contentType); // same as res.type(_contentType);
res.setHeader('x-attachment-name', data.response.headers['x-attachment-name']);
const name = hdrGetName(data.response.headers);
resSetName(res, name); // pass it along
// res.set({
// 'Content-Disposition': `${data.response.headers['content-disposition']}`});
let file = null;
// if (/.*\/xml/.test(_contentType)) {
// _contentType of XML.XML is actually 'application/octet', so that doesn't work.
// Since we control back-end, we could change it to 'text/xml' ??
if (name === 'XML.XML') {
// Our best guess is that CDA.xsl will be in the parent directory (i.e., shared)
file = new Buffer(xmlCDA(data.responseBody, hrefCDAExport), 'utf8');
} else {
file = new Buffer(data.responseBody, 'utf8');
}
logdbg('/exportAttachment file.byteLength=', file.byteLength);
res.status(data.response.statusCode).send(file);
})
.catch(error => console.error(error));
});
// /attachment/exportAsPDF
router.post('/exportAsPDF', (req, res) => {
logdbg('/exportAsPDF post req.body=', req.body);
logdbg(`/exportAsPDF rest.postResource("${process.env.END_POINT_BASE_URI}/api/v1/exportAsPDF", req.body)`);
// const attachIdLX = req.body.attachIdLX;
rest.postResource(`${process.env.END_POINT_BASE_URI}/ars/api/v1/exportAsPDF`, req.body)
.then(value => {
// logdbg('value.response=', value.response);
logdbg('/exportAsPDF value => value.responseBody=', value.responseBody);
if (!value.responseBody) {
// Error occured
logerr('/exportAsPDF error: responseBody undefined');
return res.status(500);
}
if (value.responseBody['errorCode']) {
// Error occured
logerr('/exportAsPDF errorCode: ', value.responseBody['errorCode']);
return res.status(500);
}
const contentType = value.response.headers['content-type'];
logdbg('content-type ', contentType);
res.contentType(contentType);
const name = hdrGetName(value.response.headers);
resSetName(res, name); // pass it along
const file = new Buffer(value.responseBody, 'utf8');
res.status(value.response.statusCode).send(file);
}).catch(reason => logerr('/exportAsPDF ', reason));
});
router.get('/xml', (req, res) => {
// /attachment/xml
// deliver an (the) XML>XML file directly to the browser
logdbg('/xml ', xmlFile);
fs.readFile(xmlFile, (err, data) => {
if (err) {
logdbg('/xml ', JSON.stringify(err));
res.status(500).json(err);
} else {
logdbg('/xml OK');
res.writeHead(200, {
'Content-Type': 'text/xml'
});
res.write(data); // already have data
res.end();
}
});
});
router.get('/cda', (req, res) => {
// /attachment/cda
// deliver a CDA.xsl file directly to the browser, to fulfill href='cda' in the xml buffer
const cdaName = 'src/assets/xml/CDA.xsl';
const cdaFile = path.join(appPath, cdaName); // an actual file on the server
logdbg('/cda ', cdaFile);
fs.readFile(cdaFile, (err, data) => {
if (err) {
logdbg('/cda ', JSON.stringify(err));
res.status(500).json(err);
} else {
logdbg('/cda OK');
res.writeHead(200, {
'Content-Type': 'text/xsl'
}); // XSL, not XML
res.write(data); // already have data
res.end();
}
});
});
router.post('/cleanXml', (req, res) => {
// ??? This only removes the single file xmlFile
// So why read the directory? just unlink it.
// fs.readdir(xmlPath, (err, files) => {
// if (err) {
// console.log('Error /cleanXml err=', err)
// }
// if (files.some(file => file === 'XML.XML')) {
// fs.unlink(path.join(xmlPath,'XML.XML'), (err) => {
// if (err) {
// console.error(err)
// return res.status(500).json({message: 'Unable to clean xml temp file'});
// }
// })
// return res.status(200).json({message: 'xml temp files have been cleaned.'});
// }
//
// })
fs.exists(xmlFile, exists => {
if (!exists) {
res.status(200).json({
message: 'No XML.XML temp files exist.'
});
} else {
fs.unlink(xmlFile, err => {
if (err) {
//
// Don't complain if file did not exist
logerr(err);
res.status(500).json({
message: 'Unable to clean xml temp file'
});
} else {
res.status(200).json({
message: 'xml temp files have been cleaned.'
});
}
});
}
});
return res;
// const filePath = path.join(__dirname, '../../../../attachmentViewerTemp/');
// fs.readdir(filePath, (err, files) => {
// if (err) {
// logerr('/cleanXml err=', err)
// }
// if (files.some(file => file === 'XML.XML')) {
// fs.unlink(filePath+'XML.XML', (err) => {
// if (err) {
// logerr(err);
// return res.status(500).json({message: 'Unable to clean xml temp file'});
// }
// })
// return res.status(200).json({message: 'xml temp files have been cleaned.'});
// }
//
// })
});
module.exports = router;