'use strict'; // modules used for graphic manipulation const jimp = require('jimp'); const imagemin = require('imagemin'); const imageminpngquant = require('imagemin-pngquant'); const sha1 = require('node-sha1'); const async = require('async'); // Mongoose Model using mongoDB const UserModel = require('../models/user'); const AwardingModel = require('../models/awarding'); const RankModel = require('../models/rank'); // http error code table const codes = require('../routes/http-codes'); // standard input/output file extension const fileExt = '.png'; const resourceDir = require('../middleware/resource-location').resourceLocation().concat('/'); let createSignature = (userId, res, next) => { let loadedImage; let user; if (!String(userId).match(/^[0-9a-fA-F]{24}$/)) { let error = new Error('no valid ID ' + userId); error.status = codes.wrongdatatyperequest; return next(error); } UserModel.findById(userId, ['username', 'rankLvl', 'squadId']) .populate('squadId', ['name', 'fraction']) .exec() .then((resUser) => { user = resUser; let platePath; if (resUser && resUser.squadId && resUser.squadId.fraction === 'BLUFOR') { platePath = __dirname + '/backplate/blufor' + fileExt; } else if (resUser && resUser.squadId && resUser.squadId.fraction === 'OPFOR') { platePath = __dirname + '/backplate/opfor' + fileExt; } // kill process on undefined platePath if (!platePath) { throw new Error('Fraction not defined for user with id ' + userId); } return jimp.read(platePath); }) .then((image) => { loadedImage = image; }).then(() => { return jimp.loadFont(jimp.FONT_SANS_16_BLACK); }) .then((font) => { loadedImage.print(font, 118, 22, user.username); }) .then(() => { return jimp.loadFont(__dirname + '/font/DEJAVU_SANS_13.fnt'); }) .then((font) => { loadedImage.print(font, 118, 49, user.squadId.name); return font; }) .then((font) => { let rankH; let rankW; let rankX; let rankY; RankModel.findOne({'level': user.rankLvl, 'fraction': user.squadId.fraction}, (err, result) => { if (err) { return next(err); } if (result) { if (user.squadId.fraction === 'BLUFOR') { rankW = 33; rankH = 80; rankX = 38; rankY = 20; } else { rankW = 38; rankH = 60; rankX = 35; rankY = 36; } jimp.read(resourceDir + 'rank/' + result._id + fileExt) .then((rankImage) => { rankImage.resize(rankW, rankH); loadedImage .print(font, 118, 69, result.name) .composite(rankImage, rankX, rankY); }) .then(() => { addDecorationsAndSave(userId, loadedImage, res, next); }); } else { // user has not any assignable rank in his fraction at this point, // e.g. assigned rank has been deleted or switched fraction so rankLvl is not defined addDecorationsAndSave(userId, loadedImage, res, next); } }); }) .catch((err) => { next(err); }); }; /** * query decorations according to user id and * add all graphics into given image * * @param {number} userId * @param {Object} loadedImage - background image * @param {function} res * @param {function} next */ let addDecorationsAndSave = (userId, loadedImage, res, next) => { const medalW = 21; const medalH = 40; const ribbonW = 53; const ribbonH = 15; let medalPx = 690; let medalPy = 9; let ribbonPx = 658; let ribbonPy = 107; AwardingModel.find({ 'userId': userId, 'confirmed': 1, }, ['decorationId', 'date']).populate('decorationId', ['isMedal', 'fraction']) .exec((err, awardings) => { if (err) { return next(err); } if (awardings.length > 0) { awardings.sort((a1, a2) => sortAwardingsForSignature(a1, a2)); // use synchronized call to keep correct order of decorations async.eachSeries(awardings, (award, callback) => { jimp.read(resourceDir + 'decoration/' + award.decorationId._id + fileExt) .then((decorationImage) => { if (award.decorationId.isMedal) { decorationImage.resize(medalW, medalH); loadedImage.composite(decorationImage, medalPx, medalPy); if (medalPy === 9) { medalPx = medalPx - 1 - medalW; } else { medalPx = medalPx + 1 + medalW; } if (medalPx <= 300) { medalPx = medalPx + 1 + medalW; medalPy = medalPy + 3 + medalH; } } else { decorationImage.resize(ribbonW, ribbonH); loadedImage.composite(decorationImage, ribbonPx, ribbonPy); ribbonPx = ribbonPx - 2 - ribbonW; if (ribbonPx <= 200) { ribbonPy = ribbonPy - 3 - ribbonH; ribbonPx = 657; } } callback(); }); }, (err) => { if (err) { throw err; } compareImagesAndSave(loadedImage, userId, res, next); }); } else { compareImagesAndSave(loadedImage, userId, res, next); } } ); }; let compareImagesAndSave = (generatedImage, userId, res, next) => { return jimp.read(resourceDir + 'signature/big/' + userId + fileExt) .then((oldImage) => { // compare hashes of image map to recognize difference const sig1 = sha1(generatedImage.bitmap.data); const sig2 = sha1(oldImage.bitmap.data); if (sig1 !== sig2) { saveJimpImageAndCompress(generatedImage, userId, res, next); } else { res.locals.items = {status: 'nothing to do'}; next(); } }) .catch((err) => { saveJimpImageAndCompress(generatedImage, userId, res, next); }); }; /** * Write Jimp image to file system * Gets minified after 3 seconds timeout * * @param {Object} image - Jimp image * @param {number} userId - user id of signature owner * @param {function} res - express response object * @param {function} next - express next-function */ let saveJimpImageAndCompress = (image, userId, res, next) => { image.write(resourceDir + 'signature/big/' + userId + fileExt); setTimeout(() => { imagemin([resourceDir + 'signature/big/' + userId + fileExt], resourceDir + 'signature/', { plugins: [ imageminpngquant({quality: '65-80'}), ], }).then((files) => { res.locals.items = {status: 'success'}; return next(); }).catch((error) => { console.log(error); }); }, 3000); }; const sortAwardingsForSignature = (a1, a2) => { if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) { if (a1.decorationId.fraction === a2.decorationId.fraction) { if (a1.date !== a2.date) { if (a1.date < a2.date) { return -1; } if (a1.date > a2.date) { return 1; } } return 0; } else { if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') { return -1; } if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') { return 1; } } } if (a1.decorationId.isMedal !== a2.decorationId.isMedal) { if (a1.decorationId.isMedal && !a2.decorationId.isMedal) { return 1; } if (!a1.decorationId.isMedal && a2.decorationId.isMedal) { return -1; } } if (a1.decorationId.isMedal && a2.decorationId.isMedal) { if (a1.date !== a2.date) { if (a1.date < a2.date) { return -1; } if (a1.date > a2.date) { return 1; } } return 0; } }; module.exports = createSignature;