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

268 lines
8.0 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 = __dirname + '/../resource/';
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(__dirname + '/font/DEVAJU_SANS_19.fnt');
})
.then((font) => {
loadedImage.print(font, 128, 8, user.username)
})
.then(() => {
return jimp.loadFont(__dirname + '/font/DEJAVU_SANS_13.fnt');
})
.then((font) => {
loadedImage.print(font, 128, 35, user.squadId.name);
return font;
})
.then((font) => {
let rankH, rankW, rankX, 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 = 25;
rankH = 60;
rankX = 36;
rankY = 34;
} else {
rankW = 37;
rankH = 58;
rankX = 30;
rankY = 34;
}
jimp.read(resourceDir + 'rank/' + result._id + fileExt)
.then((rankImage) => {
rankImage.resize(rankW, rankH);
loadedImage
.print(font, 128, 55, 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 userId
* @param loadedImage - background image
* @param res
* @param next
*/
let addDecorationsAndSave = (userId, loadedImage, res, next) => {
const medalW = 21;
const medalH = 40;
const ribbonW = 53;
const ribbonH = 15;
let medalPx = 630;
let medalPy = 5;
let ribbonPx = 598;
let ribbonPy = 95;
AwardingModel.find({
'userId': userId,
'confirmed': 1
}, ['decorationId', 'date']).populate('decorationId', ['isMedal', 'fraction'])
.exec((err, awardings) => {
if (err) {
return next(err);
}
if (awardings.length > 0) {
//TODO: simplify this sorting hell
awardings.sort((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;
}
});
// 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 === 5) {
medalPx = medalPx - 1 - medalW;
} else {
medalPx = medalPx + 1 + medalW;
}
if (medalPx <= 300) {
medalPy = medalPy + 3 + medalH;
}
} else {
decorationImage.resize(ribbonW, ribbonH);
loadedImage.composite(decorationImage, ribbonPx, ribbonPy);
ribbonPx = ribbonPx - 2 - ribbonW;
if (ribbonPx <= 154) {
ribbonPy = ribbonPy - 3 - ribbonH;
ribbonPx = 598;
}
}
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 image - Jimp image
* @param userId - user id of signature owner
* @param res - express response object
* @param 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);
};
module.exports = createSignature;