| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const express = require('express'); | 
					
						
							|  |  |  | const router = express.Router(); | 
					
						
							|  |  |  | const sql = require('../../services/sql'); | 
					
						
							|  |  |  | const auth = require('../../services/auth'); | 
					
						
							|  |  |  | const utils = require('../../services/utils'); | 
					
						
							| 
									
										
										
										
											2018-01-06 15:56:00 -05:00
										 |  |  | const sync_table = require('../../services/sync_table'); | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | const multer = require('multer')(); | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  | const imagemin = require('imagemin'); | 
					
						
							|  |  |  | const imageminMozJpeg = require('imagemin-mozjpeg'); | 
					
						
							| 
									
										
										
										
											2018-01-06 13:53:02 -05:00
										 |  |  | const imageminPngQuant = require('imagemin-pngquant'); | 
					
						
							| 
									
										
										
										
											2018-01-07 09:22:55 -05:00
										 |  |  | const imageminGifLossy = require('imagemin-giflossy'); | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  | const jimp = require('jimp'); | 
					
						
							| 
									
										
										
										
											2018-01-07 08:24:04 -05:00
										 |  |  | const imageType = require('image-type'); | 
					
						
							|  |  |  | const sanitizeFilename = require('sanitize-filename'); | 
					
						
							| 
									
										
										
										
											2018-01-07 09:35:44 -05:00
										 |  |  | const wrap = require('express-promise-wrap').wrap; | 
					
						
							| 
									
										
										
										
											2018-01-07 14:07:59 -05:00
										 |  |  | const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; | 
					
						
							|  |  |  | const fs = require('fs'); | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-07 09:59:05 -05:00
										 |  |  | router.get('/:imageId/:filename', auth.checkApiAuthOrElectron, wrap(async (req, res, next) => { | 
					
						
							| 
									
										
										
										
											2018-01-28 19:30:14 -05:00
										 |  |  |     const image = await sql.getFirst("SELECT * FROM images WHERE imageId = ?", [req.params.imageId]); | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!image) { | 
					
						
							|  |  |  |         return res.status(404).send({}); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-01-07 14:07:59 -05:00
										 |  |  |     else if (image.data === null) { | 
					
						
							|  |  |  |         res.set('Content-Type', 'image/png'); | 
					
						
							|  |  |  |         return res.send(fs.readFileSync(RESOURCE_DIR + '/db/image-deleted.png')); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     res.set('Content-Type', 'image/' + image.format); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     res.send(image.data); | 
					
						
							| 
									
										
										
										
											2018-01-07 09:35:44 -05:00
										 |  |  | })); | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-07 09:59:05 -05:00
										 |  |  | router.post('', auth.checkApiAuthOrElectron, multer.single('upload'), wrap(async (req, res, next) => { | 
					
						
							| 
									
										
										
										
											2018-01-28 19:30:14 -05:00
										 |  |  |     const sourceId = req.headers.sourceId; | 
					
						
							| 
									
										
										
										
											2018-01-06 21:49:02 -05:00
										 |  |  |     const noteId = req.query.noteId; | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  |     const file = req.file; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-28 19:30:14 -05:00
										 |  |  |     const note = await sql.getFirst("SELECT * FROM notes WHERE noteId = ?", [noteId]); | 
					
						
							| 
									
										
										
										
											2018-01-06 21:49:02 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!note) { | 
					
						
							| 
									
										
										
										
											2018-01-07 09:22:55 -05:00
										 |  |  |         return res.status(404).send(`Note ${noteId} doesn't exist.`); | 
					
						
							| 
									
										
										
										
											2018-01-06 21:49:02 -05:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-07 09:22:55 -05:00
										 |  |  |     if (!["image/png", "image/jpeg", "image/gif"].includes(file.mimetype)) { | 
					
						
							|  |  |  |         return res.status(400).send("Unknown image type: " + file.mimetype); | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const now = utils.nowDate(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  |     const resizedImage = await resize(file.buffer); | 
					
						
							|  |  |  |     const optimizedImage = await optimize(resizedImage); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-07 08:24:04 -05:00
										 |  |  |     const imageFormat = imageType(optimizedImage); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const fileNameWithouExtension = file.originalname.replace(/\.[^/.]+$/, ""); | 
					
						
							|  |  |  |     const fileName = sanitizeFilename(fileNameWithouExtension + "." + imageFormat.ext); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 21:49:02 -05:00
										 |  |  |     const imageId = utils.newImageId(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 15:56:00 -05:00
										 |  |  |     await sql.doInTransaction(async () => { | 
					
						
							|  |  |  |         await sql.insert("images", { | 
					
						
							| 
									
										
										
										
											2018-01-28 19:30:14 -05:00
										 |  |  |             imageId: imageId, | 
					
						
							| 
									
										
										
										
											2018-01-07 08:24:04 -05:00
										 |  |  |             format: imageFormat.ext, | 
					
						
							|  |  |  |             name: fileName, | 
					
						
							| 
									
										
										
										
											2018-01-06 15:56:00 -05:00
										 |  |  |             checksum: utils.hash(optimizedImage), | 
					
						
							|  |  |  |             data: optimizedImage, | 
					
						
							| 
									
										
										
										
											2018-01-28 19:30:14 -05:00
										 |  |  |             isDeleted: 0, | 
					
						
							|  |  |  |             dateModified: now, | 
					
						
							|  |  |  |             dateCreated: now | 
					
						
							| 
									
										
										
										
											2018-01-06 15:56:00 -05:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         await sync_table.addImageSync(imageId, sourceId); | 
					
						
							| 
									
										
										
										
											2018-01-06 21:49:02 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const noteImageId = utils.newNoteImageId(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-28 19:38:05 -05:00
										 |  |  |         await sql.insert("note_images", { | 
					
						
							| 
									
										
										
										
											2018-01-28 19:30:14 -05:00
										 |  |  |             noteImageId: noteImageId, | 
					
						
							|  |  |  |             noteId: noteId, | 
					
						
							|  |  |  |             imageId: imageId, | 
					
						
							|  |  |  |             isDeleted: 0, | 
					
						
							|  |  |  |             dateModified: now, | 
					
						
							|  |  |  |             dateCreated: now | 
					
						
							| 
									
										
										
										
											2018-01-06 21:49:02 -05:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         await sync_table.addNoteImageSync(noteImageId, sourceId); | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     res.send({ | 
					
						
							|  |  |  |         uploaded: true, | 
					
						
							| 
									
										
										
										
											2018-01-07 08:24:04 -05:00
										 |  |  |         url: `/api/images/${imageId}/${fileName}` | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-01-07 09:35:44 -05:00
										 |  |  | })); | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  | const MAX_SIZE = 1000; | 
					
						
							| 
									
										
										
										
											2018-01-06 13:53:02 -05:00
										 |  |  | const MAX_BYTE_SIZE = 200000; // images should have under 100 KBs
 | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | async function resize(buffer) { | 
					
						
							|  |  |  |     const image = await jimp.read(buffer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (image.bitmap.width > image.bitmap.height && image.bitmap.width > MAX_SIZE) { | 
					
						
							|  |  |  |         image.resize(MAX_SIZE, jimp.AUTO); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (image.bitmap.height > MAX_SIZE) { | 
					
						
							|  |  |  |         image.resize(jimp.AUTO, MAX_SIZE); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-01-06 13:53:02 -05:00
										 |  |  |     else if (buffer.byteLength <= MAX_BYTE_SIZE) { | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  |         return buffer; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // we do resizing with max quality which will be trimmed during optimization step next
 | 
					
						
							|  |  |  |     image.quality(100); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-07 08:26:42 -05:00
										 |  |  |     // when converting PNG to JPG we lose alpha channel, this is replaced by white to match Trilium white background
 | 
					
						
							|  |  |  |     image.background(0xFFFFFFFF); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  |     // getBuffer doesn't support promises so this workaround
 | 
					
						
							|  |  |  |     return await new Promise((resolve, reject) => image.getBuffer(jimp.MIME_JPEG, (err, data) => { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |             reject(err); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |             resolve(data); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function optimize(buffer) { | 
					
						
							|  |  |  |     return await imagemin.buffer(buffer, { | 
					
						
							|  |  |  |         plugins: [ | 
					
						
							| 
									
										
										
										
											2018-01-07 08:24:04 -05:00
										 |  |  |             imageminMozJpeg({ | 
					
						
							|  |  |  |                 quality: 50 | 
					
						
							|  |  |  |             }), | 
					
						
							| 
									
										
										
										
											2018-01-06 13:53:02 -05:00
										 |  |  |             imageminPngQuant({ | 
					
						
							|  |  |  |                 quality: "0-70" | 
					
						
							| 
									
										
										
										
											2018-01-07 09:22:55 -05:00
										 |  |  |             }), | 
					
						
							|  |  |  |             imageminGifLossy({ | 
					
						
							|  |  |  |                 lossy: 80, | 
					
						
							|  |  |  |                 optimize: '3' // needs to be string
 | 
					
						
							| 
									
										
										
										
											2018-01-06 12:38:25 -05:00
										 |  |  |             }) | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-05 23:54:02 -05:00
										 |  |  | module.exports = router; |