268 lines
9.0 KiB
JavaScript
268 lines
9.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 = 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(__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;
|
|
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 = 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 {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 = 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) {
|
|
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 === 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 {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;
|