Compare commits

..

No commits in common. "141a340aadad4f23dbb1c8c61d656a1f3ecb4431" and "557d0de60a161c39a848167107c78bd5882f0f76" have entirely different histories.

15 changed files with 431 additions and 773 deletions

6
.gitignore vendored
View File

@ -4,7 +4,7 @@
dist/ dist/
tmp/ tmp/
etc/ etc/
server/apib/documentation.apib api/apib/documentation.apib
# dependencies # dependencies
node_modules node_modules
@ -47,8 +47,8 @@ Thumbs.db
# Internal Data # Internal Data
public/ public/
mongodb-data/ mongodb-data/
server/resource/ api/resource/
server/apib/dredd/data/tmp-resource api/apib/dredd/data/tmp-resource
backup/ backup/
# System # System

View File

@ -3,9 +3,6 @@
// HTTP status codes by name // HTTP status codes by name
const codes = require('../routes/http-codes'); const codes = require('../routes/http-codes');
// library to check image dimensions from file buffer
const sizeOf = require('buffer-image-size');
/** /**
* check if id has valid UUID format * check if id has valid UUID format
* *
@ -25,14 +22,4 @@ const idValidator = (req, res, next) => {
next(); next();
}; };
const imageDimensionValidator = (imageFileBuf, maxWidth, maxHeight) => {
const dimensions = sizeOf(imageFileBuf);
if (dimensions.width > maxWidth || dimensions.height > maxHeight) {
let err = new Error(`Image exceeds maximum dimensions of ${maxWidth}px width and ${maxHeight}px height`);
err.status = codes.wrongrequest;
return err;
}
};
exports.idValidator = idValidator; exports.idValidator = idValidator;
exports.imageDimensionValidator = imageDimensionValidator;

814
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,25 +21,24 @@
"dependencies": { "dependencies": {
"async": "^2.5.0", "async": "^2.5.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"body-parser": "^1.18.3", "body-parser": "~1.13.2",
"buffer-image-size": "^0.6.4",
"cors": "^2.8.4", "cors": "^2.8.4",
"cron": "^1.3.0", "cron": "^1.3.0",
"debug": "^3.1.0", "debug": "^3.1.0",
"express": "^4.16.2", "express": "^4.16.2",
"imagemin": "^5.2.2", "imagemin": "^5.2.2",
"imagemin-pngquant": "^5.0.0", "imagemin-pngquant": "^5.0.0",
"jimp": "^0.6.0", "jimp": "^0.2.27",
"jsonwebtoken": "^7.4.3", "jsonwebtoken": "^7.4.3",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mongoose": "^5.0.3", "mongoose": "^5.0.3",
"morgan": "^1.9.1", "morgan": "~1.6.1",
"multer": "^1.3.0", "multer": "^1.3.0",
"node-html-parser": "^1.1.10", "node-html-parser": "^1.1.10",
"node-sha1": "^1.0.1", "node-sha1": "^1.0.1",
"q": "^1.5.0", "q": "^1.5.0",
"serve-favicon": "^2.5.0", "serve-favicon": "~2.3.0",
"supports-color": "^5.1.0" "supports-color": "^5.1.0"
}, },
"devDependencies": { "devDependencies": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -13,7 +13,6 @@ const codes = require('./http-codes');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware'); const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkHl = require('../middleware/permission-check').checkHl; const checkHl = require('../middleware/permission-check').checkHl;
const genericGetById = require('./_generic').genericGetById;
const routerHandling = require('../middleware/router-handling'); const routerHandling = require('../middleware/router-handling');
const idValidator = require('../middleware/validators').idValidator; const idValidator = require('../middleware/validators').idValidator;
const resourceLocation = require('../middleware/resource-location').resourceLocation().concat('/decoration/'); const resourceLocation = require('../middleware/resource-location').resourceLocation().concat('/decoration/');
@ -23,175 +22,140 @@ const DecorationModel = require('../models/decoration');
const AwardingsModel = require('../models/awarding'); const AwardingsModel = require('../models/awarding');
// util // util
const imageDimensionValidator = require('../middleware/validators').imageDimensionValidator; const genericGetById = require('./_generic').genericGetById;
const MAX_MEDAL_IMAGE_WIDTH = 100;
const MAX_MEDAL_IMAGE_HEIGHT = 150;
const MAX_RIBBON_IMAGE_WIDTH = 150;
const MAX_RIBBON_IMAGE_HEIGHT = 50;
const decorationRouter = new express.Router(); const decoration = new express.Router();
// routes ********************** // routes **********************
decorationRouter.route('/') decoration.route('/')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {}; const filter = {};
if (req.query.fractFilter) { if (req.query.fractFilter) {
filter.fraction = req.query.fractFilter.toUpperCase(); filter.fraction = req.query.fractFilter.toUpperCase();
} }
if (req.query.q) { if (req.query.q) {
filter.name = {$regex: req.query.q, $options: 'i'}; filter.name = {$regex: req.query.q, $options: 'i'};
} }
DecorationModel.find(filter, {}, { DecorationModel.find(filter, {}, {
sort: { sort: {
fraction: 'asc', fraction: 'asc',
isMedal: 'asc', isMedal: 'asc',
sortingNumber: 'asc', sortingNumber: 'asc',
name: 'asc', name: 'asc',
}, },
}, (err, items) => { }, (err, items) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
if (items && items.length > 0) { if (items && items.length > 0) {
res.locals.items = items; res.locals.items = items;
} else { } else {
res.locals.items = []; res.locals.items = [];
} }
res.locals.processed = true; res.locals.processed = true;
next(); next();
}); });
}) })
.post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
if (req.file) { const decoration = new DecorationModel(req.body);
const decoration = new DecorationModel(req.body); // timestamp and default are set automatically by Mongoose Schema Validation
decoration.save((err) => {
if (err) {
err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err);
}
res.status(codes.created);
res.locals.items = decoration;
fs.appendFile(resourceLocation + decoration._id + '.png',
new Buffer(req.file.buffer),
(err) => {
if (err) next(err);
});
next();
});
})
const imageFileBuffer = req.file.buffer; .all(
const err = imageDimensionValidator(imageFileBuffer, routerHandling.httpMethodNotAllowed
(decoration.isMedal ? MAX_MEDAL_IMAGE_WIDTH : MAX_RIBBON_IMAGE_WIDTH), );
(decoration.isMedal ? MAX_MEDAL_IMAGE_HEIGHT : MAX_RIBBON_IMAGE_HEIGHT));
if (err) {
return next(err);
}
// timestamp and default are set automatically by Mongoose Schema Validation decoration.route('/:id')
decoration.save((err) => { .get(idValidator, (req, res, next) => {
if (err) { return genericGetById(req, res, next, DecorationModel);
err.status = codes.wrongrequest; })
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err);
}
res.status(codes.created);
res.locals.items = decoration;
fs.appendFile(resourceLocation + decoration._id + '.png',
new Buffer(req.file.buffer),
(err) => {
if (err) next(err);
});
next();
});
} else {
const err = new Error('no image file provided');
err.status = codes.wrongmediasend;
next(err);
}
})
.all( .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
routerHandling.httpMethodNotAllowed if (!req.body || (req.body._id && req.body._id !== req.params.id)) {
); // little bit different as in PUT. :id does not need to be in data, but if the _id and url id must match
const err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + ' ' +
req.body._id);
err.status = codes.notfound;
next(err);
return; // prevent node to process this function further after next() has finished.
}
decorationRouter.route('/:id') // optional task 3: increment version manually as we do not use .save(.)
.get(idValidator, (req, res, next) => { req.body.updatedAt = new Date();
return genericGetById(req, res, next, DecorationModel); req.body.$inc = {__v: 1};
})
.patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { if (req.file) {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) { const file = resourceLocation + req.params.id + '.png';
// little bit different as in PUT. :id does not need to be in data, but if the _id and url id must fs.unlink(file, (err) => {
// match if (err) next(err);
const err = new Error( fs.appendFile(file, new Buffer(req.file.buffer), (err) => {
'id of PATCH resource and send JSON body are not equal ' + req.params.id + ' ' + if (err) next(err);
req.body._id); });
err.status = codes.notfound; });
next(err); }
return; // prevent node to process this function further after next() has finished.
}
// optional task 3: increment version manually as we do not use .save(.) // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need
req.body.updatedAt = new Date(); // to reset attributes that are missing.
req.body.$inc = {__v: 1}; DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) {
err.status = codes.wrongrequest;
} else if (!item) {
err = new Error('item not found');
err.status = codes.notfound;
} else {
res.locals.items = item;
}
next(err);
});
})
DecorationModel.findById(req.body._id, (err, item) => { .delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
if (err) { const id = req.params.id;
return next(err); DecorationModel.findByIdAndRemove(id, (err, item) => {
} if (err) {
if (req.file) { err.status = codes.wrongrequest;
const imageFileBuffer = req.file.buffer; } else if (!item) {
const imageDimensionError = imageDimensionValidator(imageFileBuffer, err = new Error('item not found');
(item.isMedal ? MAX_MEDAL_IMAGE_WIDTH : MAX_RIBBON_IMAGE_WIDTH), err.status = codes.notfound;
(item.isMedal ? MAX_MEDAL_IMAGE_HEIGHT : MAX_RIBBON_IMAGE_HEIGHT)); }
if (imageDimensionError) {
return next(imageDimensionError);
}
const file = resourceLocation + req.params.id + '.png'; // deleted all awardings linked to this decoration
fs.unlink(file, (err) => { AwardingsModel.find({decorationId: id}).remove().exec();
if (err) next(err);
fs.appendFile(file, new Buffer(req.file.buffer), (err) => {
if (err) next(err);
});
});
}
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no // delete graphic
// need to reset attributes that are missing. fs.unlink(resourceLocation.concat(id).concat('.png'),
DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { (err) => {
if (err) { // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
err.status = codes.wrongrequest; res.locals.processed = true;
} else if (!item) { next(err);
err = new Error('item not found'); });
err.status = codes.notfound; });
} else { })
res.locals.items = item;
}
next(err);
});
});
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .all(
const id = req.params.id; routerHandling.httpMethodNotAllowed
DecorationModel.findByIdAndRemove(id, (err, item) => { );
if (err) {
err.status = codes.wrongrequest;
} else if (!item) {
err = new Error('item not found');
err.status = codes.notfound;
}
// deleted all awardings linked to this decoration
AwardingsModel.find({decorationId: id}).remove().exec();
// delete graphic
fs.unlink(resourceLocation.concat(id).concat('.png'),
(err) => {
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last
// handler
res.locals.processed = true;
next(err);
});
});
})
.all(
routerHandling.httpMethodNotAllowed
);
// this middleware function can be used, if you like or remove it // this middleware function can be used, if you like or remove it
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json // it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
decorationRouter.use(routerHandling.emptyResponse); decoration.use(routerHandling.emptyResponse);
module.exports = decorationRouter; module.exports = decoration;

View File

@ -22,9 +22,6 @@ const RankModel = require('../models/rank');
// util // util
const genericGetById = require('./_generic').genericGetById; const genericGetById = require('./_generic').genericGetById;
const imageDimensionValidator = require('../middleware/validators').imageDimensionValidator;
const MAX_IMAGE_WIDTH = 120;
const MAX_IMAGE_HEIGHT = 120;
const ranks = new express.Router(); const ranks = new express.Router();
@ -56,32 +53,20 @@ ranks.route('/')
}) })
.post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
if (req.file) { const rank = new RankModel(req.body);
const imageFileBuffer = req.file.buffer; // timestamp and default are set automatically by Mongoose Schema Validation
const err = imageDimensionValidator(imageFileBuffer, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT); rank.save((err) => {
if (err) { if (err) {
return next(err); err.status = codes.wrongrequest;
next(err);
} }
res.status(codes.created);
const rank = new RankModel(req.body); res.locals.items = rank;
// timestamp and default are set automatically by Mongoose Schema Validation fs.appendFile(resourceLocation + rank._id + '.png', new Buffer(req.file.buffer),
rank.save((err) => { (err) => {
if (err) {
err.status = codes.wrongrequest;
next(err); next(err);
} });
res.status(codes.created); });
res.locals.items = rank;
fs.appendFile(resourceLocation + rank._id + '.png', new Buffer(imageFileBuffer),
(err) => {
next(err);
});
});
} else {
const err = new Error('no image file provided');
err.status = codes.wrongmediasend;
next(err);
}
}) })
.all( .all(
@ -109,16 +94,10 @@ ranks.route('/:id')
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
if (req.file) { if (req.file) {
const imageFileBuffer = req.file.buffer;
const err = imageDimensionValidator(imageFileBuffer, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT);
if (err) {
return next(err);
}
const file = resourceLocation + req.params.id + '.png'; const file = resourceLocation + req.params.id + '.png';
fs.unlink(file, (err) => { fs.unlink(file, (err) => {
if (err) next(err); if (err) next(err);
fs.appendFile(file, new Buffer(imageFileBuffer), fs.appendFile(file, new Buffer(req.file.buffer),
(err) => { (err) => {
if (err) next(err); if (err) next(err);
}); });

View File

@ -22,9 +22,6 @@ const SquadModel = require('../models/squad');
// util // util
const genericGetById = require('./_generic').genericGetById; const genericGetById = require('./_generic').genericGetById;
const imageDimensionValidator = require('../middleware/validators').imageDimensionValidator;
const MAX_IMAGE_WIDTH = 150;
const MAX_IMAGE_HEIGHT = 150;
const squads = new express.Router(); const squads = new express.Router();
@ -57,12 +54,6 @@ squads.route('/')
const squad = new SquadModel(req.body); const squad = new SquadModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
if (req.file) { if (req.file) {
const imageFileBuffer = req.file.buffer;
const err = imageDimensionValidator(imageFileBuffer, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT);
if (err) {
return next(err);
}
squad.save((err) => { squad.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
@ -71,9 +62,10 @@ squads.route('/')
} }
res.status(codes.created); res.status(codes.created);
res.locals.items = squad; res.locals.items = squad;
fs.appendFile(resourceLocation.concat(squad._id).concat('.png'), new Buffer(imageFileBuffer), (err) => { fs.appendFile(resourceLocation.concat(squad._id).concat('.png'),
next(err); new Buffer(req.file.buffer), (err) => {
}); next(err);
});
}); });
} else { } else {
const err = new Error('no image file provided'); const err = new Error('no image file provided');
@ -106,16 +98,11 @@ squads.route('/:id')
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
if (req.file) { if (req.file) {
const imageFileBuffer = req.file.buffer;
const err = imageDimensionValidator(imageFileBuffer, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT);
if (err) {
return next(err);
}
const file = resourceLocation.concat(req.params.id) const file = resourceLocation.concat(req.params.id)
.concat('.png'); .concat('.png');
fs.unlink(file, (err) => { fs.unlink(file, (err) => {
if (err) next(err); if (err) next(err);
fs.appendFile(file, new Buffer(imageFileBuffer), (err) => { fs.appendFile(file, new Buffer(req.file.buffer), (err) => {
if (err) next(err); if (err) next(err);
}); });
}); });

View File

@ -66,7 +66,7 @@ export class EditDecorationComponent implements OnInit, OnDestroy {
if (this.fileList) { if (this.fileList) {
file = this.fileList[0]; file = this.fileList[0];
this.decorationService.submitDecoration(this.decoration, file) this.decorationService.submitDecoration(this.decoration, file)
.subscribe(decoration => { .subscribe(rank => {
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
}); });
} else { } else {
@ -83,15 +83,12 @@ export class EditDecorationComponent implements OnInit, OnDestroy {
} }
delete this.decoration['__v']; delete this.decoration['__v'];
this.decorationService.submitDecoration(this.decoration, file) this.decorationService.submitDecoration(this.decoration, file)
.subscribe(decoration => { .subscribe(rank => {
setTimeout(() => { setTimeout(() => {
this.imagePreviewSrc = 'resource/decoration/' + this.decoration._id + '.png?' + Date.now(); this.imagePreviewSrc = 'resource/decoration/' + this.decoration._id + '.png?' + Date.now();
}, 300); }, 300);
fileInput.value = ''; fileInput.value = '';
this.snackBarService.showSuccess('generic.save.success'); this.snackBarService.showSuccess('generic.save.success');
}, error => {
const errorMsg = error._body ? JSON.parse(error._body).error.message : error.error.error.message;
this.snackBarService.showError('Error: '.concat(errorMsg), 15000);
}); });
} }
} }

View File

@ -74,9 +74,9 @@ export class EditRankComponent implements OnInit, OnDestroy {
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
}); });
} else { } else {
this.translate.get('ranks.submit.field.image').subscribe((fieldNameImage) => { this.translate.get('ranks.submit.field.image').subscribe((fieldNameIMage) => {
this.translate.get('public.error.message.required', this.translate.get('public.error.message.required',
{fieldName: fieldNameImage}).subscribe((message) => { {fieldName: fieldNameIMage}).subscribe((message) => {
this.snackBarService.showError(message, 4000); this.snackBarService.showError(message, 4000);
}) })
}); });
@ -93,9 +93,6 @@ export class EditRankComponent implements OnInit, OnDestroy {
}, 300); }, 300);
fileInput.value = ''; fileInput.value = '';
this.snackBarService.showSuccess('generic.save.success'); this.snackBarService.showSuccess('generic.save.success');
}, error => {
const errorMsg = error._body ? JSON.parse(error._body).error.message : error.error.error.message;
this.snackBarService.showError('Error: '.concat(errorMsg), 15000);
}); });
} }
} }

View File

@ -69,7 +69,7 @@ export class EditSquadComponent implements OnInit, OnDestroy {
if (this.fileList) { if (this.fileList) {
file = this.fileList[0]; file = this.fileList[0];
this.squadService.submitSquad(this.squad, file) this.squadService.submitSquad(this.squad, file)
.subscribe(squad => { .subscribe(rank => {
this.saved = true; this.saved = true;
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
}); });
@ -87,15 +87,12 @@ export class EditSquadComponent implements OnInit, OnDestroy {
} }
delete this.squad['__v']; delete this.squad['__v'];
this.squadService.submitSquad(this.squad, file) this.squadService.submitSquad(this.squad, file)
.subscribe(squad => { .subscribe(rank => {
setTimeout(() => { setTimeout(() => {
this.imagePreviewSrc = 'resource/squad/' + this.squad._id + '.png?' + Date.now(); this.imagePreviewSrc = 'resource/squad/' + this.squad._id + '.png?' + Date.now();
}, 300); }, 300);
fileInput.value = ''; fileInput.value = '';
this.snackBarService.showSuccess('generic.save.success'); this.snackBarService.showSuccess('generic.save.success');
}, error => {
const errorMsg = error._body ? JSON.parse(error._body).error.message : error.error.error.message;
this.snackBarService.showError('Error: '.concat(errorMsg), 15000);
}); });
} }
} }

View File

@ -54,6 +54,7 @@ export class StatisticComponent implements OnInit {
} }
} else if (url.includes('right:war')) { } else if (url.includes('right:war')) {
const id = idFetchPattern.exec(url)[1]; const id = idFetchPattern.exec(url)[1];
console.log(id)
this.campaignService.getCampaignByWarId(id).subscribe((campaign) => { this.campaignService.getCampaignByWarId(id).subscribe((campaign) => {
this.switchCampaign(campaign); this.switchCampaign(campaign);
}); });