Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDF Generation #47

Merged
merged 19 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"ignorePatterns": ["pdfkit.js"],
"extends": "airbnb",
"parser": "babel-eslint",
"plugins": ["lodash"],
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
16.20.1
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ sudo: false
language: node_js

node_js:
- 12.14.0
- 16.20.1
- node

cache: yarn
Expand All @@ -29,6 +29,7 @@ services:
script:
- yarn run lint
- ./artifact.sh
- yarn test

matrix:
allow_failures:
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
### Stage 0 - Base image
FROM node:12.14.0-alpine as base
FROM node:16.20.1-alpine as base
WORKDIR /app
RUN apk --no-cache update && \
apk --no-cache upgrade && \
apk add --no-cache --virtual .build-dependencies python make g++ && \
apk add --no-cache --virtual .build-dependencies python3 make g++ && \
mkdir -p node_modules && chown -R node:node .


Expand Down
27 changes: 12 additions & 15 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
/* eslint no-restricted-syntax: [0, "ForInStatement"] */

import _ from 'lodash';
import fs from 'fs';
import http from 'http';
import https from 'https';
import express from 'express';
import bodyParser from 'body-parser';

import userDataHandler from './lib/userDataHandler';
import userReportHandler from './lib/userReportHandler';
import { exportTimeout, register, logMaker } from './lib/utils';
const _ = require('lodash');
const fs = require('fs');
const http = require('http');
const https = require('https');
const express = require('express');
const bodyParser = require('body-parser');
const { createTerminus } = require('@godaddy/terminus');
const { exportTimeout, register, logMaker } = require('./lib/utils');
const handlers = require('./lib/handlers');

export const log = logMaker('app.js', {
level: process.env.DEBUG_LEVEL || 'info',
});

const { createTerminus } = require('@godaddy/terminus');

function maybeReplaceWithContentsOfFile(obj, field) {
const potentialFile = obj[field];
if (potentialFile != null && fs.existsSync(potentialFile)) {
Expand Down Expand Up @@ -54,9 +51,9 @@ app.use(
}),
);

app.get('/export/:userid', userDataHandler());

app.post('/export/report/:userid', userReportHandler());
app.get('/export/:userid', handlers.getUserData());
app.get('/export/report/:userid', handlers.getUserReport());
app.post('/export/report/:userid', handlers.postUserReport());

function beforeShutdown() {
return new Promise((resolve) => {
Expand Down
75 changes: 75 additions & 0 deletions lib/handlers/getUserReportsHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const fs = require('fs');
const {
getSessionHeader,
createCounter,
exportTimeout,
logMaker,
} = require('../utils');

const { generateReport } = require('../reportUtils');

const reportStatusCount = createCounter(
'tidepool_get_report_status_count',
'The number of errors for each status code.',
['status_code', 'report_params'],
);

const log = logMaker('getUserReportsHandler.js', {
level: process.env.DEBUG_LEVEL || 'info',
});

module.exports = function getUserReportsHandler() {
return async (req, res) => {
// Set the timeout for the request. Make it 10 seconds longer than
// our configured timeout to give the service time to cancel the API data
// request, and close the outgoing data stream cleanly.
req.setTimeout(exportTimeout + 10000);

try {
const userId = req.params.userid;
const {
dob,
fullName,
mrn,
bgUnits,
tzName,
restricted_token: restrictedToken,
reports,
} = req.query;

const timer = setTimeout(() => {
res.emit('timeout', exportTimeout);
}, exportTimeout);

const pdfReport = await generateReport(
log,
{
userId,
fullName,
dob,
mrn,
},
{ tzName, bgUnits, reports },
{ token: restrictedToken, sessionHeader: getSessionHeader(req) },
);

res.setHeader('Content-Disposition', 'attachment: filename="report.pdf"');
res.setHeader('Content-Type', 'application/octet-stream');
const blobArrayBuffer = await pdfReport.blob.arrayBuffer();
const pdfBuffer = Buffer.from(blobArrayBuffer);
fs.writeFileSync('test.pdf', pdfBuffer);
res.send(pdfBuffer);
clearTimeout(timer);
} catch (error) {
if (error.response && error.response.status === 403) {
reportStatusCount.inc({ status_code: 403, report_params: req.query });
res.status(403);
log.error(`403: ${error}`);
} else {
reportStatusCount.inc({ status_code: 500, report_params: req.query });
res.status(500);
log.error(`500: ${error}`);
}
}
};
};
9 changes: 9 additions & 0 deletions lib/handlers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const getUserReportsHandler = require('./getUserReportsHandler');
const postUserReportsHandler = require('./postUserReportsHandler');
const userDataHandler = require('./userDataHandler');

module.exports = {
postUserReport: postUserReportsHandler,
getUserReport: getUserReportsHandler,
getUserData: userDataHandler,
};
63 changes: 63 additions & 0 deletions lib/handlers/postUserReportsHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const fs = require('fs');
const {
getSessionHeader,
createCounter,
exportTimeout,
logMaker,
} = require('../utils');

const { generateReport } = require('../reportUtils');

const reportStatusCount = createCounter(
'tidepool_post_report_status_count',
'The number of errors for each status code.',
['status_code', 'report_params'],
);

const log = logMaker('postUserReportsHandler.js', {
level: process.env.DEBUG_LEVEL || 'info',
});

module.exports = function postUserReportsHandler() {
return async (req, res) => {
// Set the timeout for the request. Make it 10 seconds longer than
// our configured timeout to give the service time to cancel the API data
// request, and close the outgoing data stream cleanly.
req.setTimeout(exportTimeout + 10000);

try {
const userId = req.params.userid;
// TODO: which token will we use here?
const restrictedToken = '';
const { userDetail, reportDetail } = req.body;

userDetail.userId = userId;
const timer = setTimeout(() => {
res.emit('timeout', exportTimeout);
}, exportTimeout);

const pdfReport = await generateReport(log, userDetail, reportDetail, {
token: restrictedToken,
sessionHeader: getSessionHeader(req),
});

res.setHeader('Content-Disposition', 'attachment: filename="report.pdf"');
res.setHeader('Content-Type', 'application/octet-stream');
const blobArrayBuffer = await pdfReport.blob.arrayBuffer();
const pdfBuffer = Buffer.from(blobArrayBuffer);
fs.writeFileSync('test.pdf', pdfBuffer);
res.send(pdfBuffer);
clearTimeout(timer);
} catch (error) {
if (error.response && error.response.status === 403) {
reportStatusCount.inc({ status_code: 403, report_params: req.query });
res.status(403);
log.error(`403: ${error}`);
} else {
reportStatusCount.inc({ status_code: 500, report_params: req.query });
res.status(500);
log.error(`500: ${error}`);
}
}
};
};
26 changes: 15 additions & 11 deletions lib/userDataHandler.js → lib/handlers/userDataHandler.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import axios from 'axios';
import queryString from 'query-string';
import dataTools from '@tidepool/data-tools';
import {
buildHeaders, createCounter, exportTimeout, logMaker,
} from './utils';
const axios = require('axios');
const queryString = require('query-string');
const dataTools = require('@tidepool/data-tools');
const {
getSessionHeader,
createCounter,
exportTimeout,
logMaker,
mmolLUnits,
} = require('../utils');

const dataStatusCount = createCounter(
'tidepool_export_status_count',
Expand All @@ -15,7 +19,7 @@ const log = logMaker('userDataHandler.js', {
level: process.env.DEBUG_LEVEL || 'info',
});

export default function userDataHandler() {
module.exports = function userDataHandler() {
return async (req, res) => {
// Set the timeout for the request. Make it 10 seconds longer than
// our configured timeout to give the service time to cancel the API data
Expand All @@ -30,7 +34,7 @@ export default function userDataHandler() {
}
if (req.query.startDate) {
queryData.startDate = req.query.startDate;
logString += ` from ${req.query.startDate}`;
logString += ` = require(${req.query.startDate}`;
}
if (req.query.endDate) {
queryData.endDate = req.query.endDate;
Expand All @@ -47,7 +51,7 @@ export default function userDataHandler() {
try {
const cancelRequest = axios.CancelToken.source();

const requestConfig = buildHeaders(req);
const requestConfig = getSessionHeader(req);
requestConfig.responseType = 'stream';
requestConfig.cancelToken = cancelRequest.token;
const dataResponse = await axios.get(
Expand All @@ -58,7 +62,7 @@ export default function userDataHandler() {
);
log.debug(`Downloading data for User ${req.params.userid}...`);

const processorConfig = { bgUnits: req.query.bgUnits || 'mmol/L' };
const processorConfig = { bgUnits: req.query.bgUnits || mmolLUnits };

let writeStream = null;

Expand Down Expand Up @@ -136,4 +140,4 @@ export default function userDataHandler() {
}
}
};
}
};
Loading