opt-cc/server/tools/signature-tool.js

269 lines
9.1 KiB
JavaScript

'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;