From bf689887abf6b1f020890974797544bd37b27e30 Mon Sep 17 00:00:00 2001 From: Jang Ryeol Date: Mon, 22 Oct 2018 16:39:19 +0900 Subject: [PATCH] Render html as png #43 --- package-lock.json | 12 +++++++- package.json | 2 ++ src/api/routes/TimetableRouter.ts | 14 +++++++++ src/core/nightmare/NightmareService.ts | 30 +++++++++++++++++++ .../timetable/TimetableImageRenderService.ts | 11 +++++++ 5 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/core/nightmare/NightmareService.ts create mode 100644 src/core/timetable/TimetableImageRenderService.ts diff --git a/package-lock.json b/package-lock.json index 390a0885..ff930b06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "snutt", - "version": "2.1.6", + "version": "2.1.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -22,6 +22,11 @@ "resolved": "https://registry.npmjs.org/@types/async/-/async-2.0.49.tgz", "integrity": "sha1-kuM9E/dMiVy5p/OLqX24Qx7RS8A=" }, + "@types/async-lock": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/@types/async-lock/-/async-lock-1.1.0.tgz", + "integrity": "sha512-Eo8EXiqmChtkt0ETf6AQ8aiDHT3Tht6OuMSa3/9nfuyqFimp7ZwPMiufsA56A7ZUGBuwFzH860jO0d8n0lETtg==" + }, "@types/bcrypt": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-2.0.0.tgz", @@ -342,6 +347,11 @@ "lodash": "^4.8.0" } }, + "async-lock": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.1.3.tgz", + "integrity": "sha512-nxlfFLGfCJ1r7p9zhR5OuL6jYkDd9P7FqSitfLji+C1NdyhCz4+rWW3kiPiyPASHhN7VlsKEvRWWbnME9lYngw==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", diff --git a/package.json b/package.json index 5a930b16..61cec4fb 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@types/async": "~2.0.49", + "@types/async-lock": "^1.1.0", "@types/bcrypt": "~2.0.0", "@types/express": "^4.0.34", "@types/express-promise-router": "^2.0.0", @@ -29,6 +30,7 @@ "@types/sinon": "^5.0.1", "assert-rejects": "^0.1.1", "async": "~2.0.1", + "async-lock": "^1.1.3", "bcrypt": "~2.0.1", "body-parser": "~1.18.3", "cookie-parser": "~1.4.3", diff --git a/src/api/routes/TimetableRouter.ts b/src/api/routes/TimetableRouter.ts index 13f36ad8..fc593f5b 100644 --- a/src/api/routes/TimetableRouter.ts +++ b/src/api/routes/TimetableRouter.ts @@ -3,6 +3,7 @@ var router = ExpressPromiseRouter(); import TimetableService = require('@app/core/timetable/TimetableService'); import TimetableLectureService = require('@app/core/timetable/TimetableLectureService'); +import TimetableImageRenderService = require('@app/core/timetable/TimetableImageRenderService'); import User from '@app/core/user/model/User'; import * as log4js from 'log4js'; import DuplicateTimetableTitleError from '@app/core/timetable/error/DuplicateTimetableTitleError'; @@ -51,6 +52,19 @@ restGet(router, '/:id')(async function(context, req) { return result; }); +router.get('/:id/image', async function(req, res, next) { + let context: RequestContext = req['context']; + let user:User = context.user; + + let table = await TimetableService.getByMongooseId(user._id, req.params.id); + if (!table) { + throw new ApiError(404, ErrorCode.TIMETABLE_NOT_FOUND, "timetable not found"); + } + let imageBuffer = await TimetableImageRenderService.renderTimetableAsPng(table, 1920, 1080); + res.contentType('image/png'); + res.send(imageBuffer); +}); + restGet(router, '/:year/:semester')(async function(context, req) { let user:User = context.user; let result = await TimetableService.getBySemester(user._id, req.params.year, req.params.semester); diff --git a/src/core/nightmare/NightmareService.ts b/src/core/nightmare/NightmareService.ts new file mode 100644 index 00000000..d4f02fe2 --- /dev/null +++ b/src/core/nightmare/NightmareService.ts @@ -0,0 +1,30 @@ +import Nightmare = require('nightmare'); +import AsyncLock = require('async-lock'); + +let nm = Nightmare({ + show: false, + executionTimeout: 1000, + waitTimeout: 1000 +}); + +let nmLock = new AsyncLock({maxPending : 100, timeout: 5000}); +const NM_LOCK_KEY = "NightmareServiceLock"; + +function execute(f: (Nightmare) => Promise): Promise { + return nmLock.acquire(NM_LOCK_KEY, function() { + return f(nm); + }); +} + +export function renderHtmlAsPng(html: string, width: number, height: number): Promise { + return execute(async function(nm) { + nm.viewport(width, height); + nm.goto("about:blank"); + await nm.wait(function (html: string){ + document.body.innerHTML = html; + return true; + }, html); + let buffer: Buffer = await nm.screenshot(); + return buffer; + }); +} diff --git a/src/core/timetable/TimetableImageRenderService.ts b/src/core/timetable/TimetableImageRenderService.ts new file mode 100644 index 00000000..80f6b5e9 --- /dev/null +++ b/src/core/timetable/TimetableImageRenderService.ts @@ -0,0 +1,11 @@ +import log4js = require('log4js'); + +import NightmareService = require('@app/core/nightmare/NightmareService'); +import Timetable from './model/Timetable'; + +let logger = log4js.getLogger(); + +export function renderTimetableAsPng(timetable: Timetable, width: number, height: number): Promise { + let html = "

Timetable title: " + timetable.title + "

"; + return NightmareService.renderHtmlAsPng(html, width, height); +}