Skip to content

Commit 74c237d

Browse files
Implement generate-document lambda (WIP)
Implement lambda to generate documents (e.g., DOW) that returns a base64 encoded string of the pdf binary. Template files are contained in a layer and are under the /templates folder. Juice is a third-party package to combine the html and css files. Still troubleshooting trying to get puppeteer to run inside of lambda using puppeteer-core and having a compressed chromium binary added as a dependency or some alternative. The chrome-aws-lambda package does not appear to be maintained anymore, but someone has forked the repo and has been updating recent versions of node (v16) and puppeteer (v14). - https://acloudguru.com/blog/engineering/serverless-browser-automation-with-aws-lambda-and-puppeteer - https://github.com/alixaxel/chrome-aws-lambda - https://github.com/Sparticuz/chrome-aws-lambda - alixaxel/chrome-aws-lambda#264 - alixaxel/chrome-aws-lambda#275 Tikcet: AT-7345
1 parent 9f371e6 commit 74c237d

File tree

5 files changed

+1990
-50
lines changed

5 files changed

+1990
-50
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { handler } from "./generate-document";
2+
3+
describe("Success", () => {
4+
it("should return successful", async () => {
5+
// GIVEN / ARRANGE
6+
// WHEN / ACT
7+
// THEN / ASSERT
8+
});
9+
});
10+
11+
describe("Failure", () => {
12+
it("should return errors", async () => {
13+
// GIVEN / ARRANGE
14+
// WHEN / ACT
15+
// THEN / ASSERT
16+
});
17+
});
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
2+
import { injectLambdaContext } from "@aws-lambda-powertools/logger";
3+
import middy from "@middy/core";
4+
import httpJsonBodyParser from "@middy/http-json-body-parser";
5+
import inputOutputLogger from "@middy/input-output-logger";
6+
import errorLogger from "@middy/error-logger";
7+
import { logger } from "../utils/logging";
8+
import { ApiBase64SuccessResponse, SuccessStatusCode } from "../utils/response";
9+
import { IpCheckerMiddleware } from "../utils/middleware/ip-logging";
10+
import { errorHandlingMiddleware } from "../utils/middleware/error-handling-middleware";
11+
import JSONErrorHandlerMiddleware from "middy-middleware-json-error-handler";
12+
import validator from "@middy/validator";
13+
import { wrapSchema } from "../utils/middleware/schema-wrapper";
14+
import {
15+
generateDocumentSchema,
16+
RequestEvent,
17+
GenerateDocumentRequest,
18+
DocumentType,
19+
} from "../models/document-generation";
20+
import * as fs from "fs";
21+
import handlebars from "handlebars";
22+
// import puppeteer from "puppeteer";
23+
import * as puppeteer from "puppeteer-core";
24+
import { PDFOptions } from "puppeteer";
25+
import chromium from "chrome-aws-lambda";
26+
import juice from "juice";
27+
28+
async function baseHandler(
29+
event: RequestEvent<GenerateDocumentRequest>
30+
): Promise<ApiBase64SuccessResponse<APIGatewayProxyResult>> {
31+
const lookingAtFiles = fs.readdirSync("/var/task/", { withFileTypes: true });
32+
// logger.debug not work ?
33+
console.debug("FILES: " + JSON.stringify(lookingAtFiles));
34+
35+
// small sample to ensure data populated in template
36+
const { documentType, templatePayload } = event.body;
37+
38+
// get files to generate documents
39+
let html, css, htmlWithCss;
40+
if (documentType === DocumentType.DESCRIPTION_OF_WORK) {
41+
html = fs.readFileSync("/opt/dow-template.html", "utf-8");
42+
css = fs.readFileSync("/opt/dow-style.css", "utf-8");
43+
htmlWithCss = juice.inlineContent(html, css);
44+
}
45+
46+
// use handlebars to populate data into template
47+
const template = handlebars.compile(htmlWithCss);
48+
const templateWithData = template(templatePayload);
49+
50+
// use puppeteer to generate pdf
51+
let browser, pdf;
52+
53+
try {
54+
// ! chromium module does not seem to be added, troubleshoot
55+
console.debug("PUPPET: " + JSON.stringify(chromium.defaultViewport));
56+
console.debug("PUPPET: " + JSON.stringify(chromium.defaultViewport));
57+
console.debug("PUPPET: " + JSON.stringify(chromium.executablePath)); // ! empty object
58+
console.debug("PUPPET: " + JSON.stringify(chromium.headless)); // true
59+
console.debug("PUPPET: " + JSON.stringify(chromium.puppeteer)); // ! undefined
60+
61+
browser = await chromium.puppeteer.launch({
62+
args: chromium.args,
63+
defaultViewport: chromium.defaultViewport,
64+
executablePath: await chromium.executablePath,
65+
headless: chromium.headless,
66+
ignoreHTTPSErrors: true,
67+
});
68+
logger.debug("BROWSER: " + JSON.stringify(browser));
69+
const page = await browser.newPage();
70+
logger.debug("PAGE: " + JSON.stringify(page));
71+
72+
const options: any = { format: "A4" }; // PDFOptions still gives typescript error
73+
74+
await page.setContent(templateWithData);
75+
await page.emulateMediaType("screen");
76+
pdf = await page.pdf(options);
77+
78+
logger.info("Document generation complete");
79+
logger.debug("PDF: " + pdf);
80+
} catch (error) {
81+
logger.error(error as any);
82+
} finally {
83+
if (browser !== null) {
84+
await browser?.close();
85+
}
86+
}
87+
88+
const headers = { "Content-type": "application/pdf" };
89+
return new ApiBase64SuccessResponse<string | undefined>(pdf?.toString("base64"), SuccessStatusCode.OK, headers);
90+
}
91+
92+
export const handler = middy(baseHandler)
93+
.use(injectLambdaContext(logger))
94+
.use(inputOutputLogger({ logger: (message) => logger.info("Event/Result", message) }))
95+
.use(errorLogger({ logger: (err) => logger.error("An error occurred during the request", err as Error) }))
96+
.use(httpJsonBodyParser())
97+
.use(validator({ eventSchema: wrapSchema(generateDocumentSchema) }));
98+
// TODO: fix middleware inputs/outputs
99+
// .use(IpCheckerMiddleware())
100+
// .use(errorHandlingMiddleware);

0 commit comments

Comments
 (0)