Merge branch 'release/v1.7.0' of hardi/opt-cc into master

pull/35/head v1.7.0
hardi 2018-03-04 20:05:44 +01:00 committed by Gogs
commit 08aeda0de3
92 changed files with 2573 additions and 2307 deletions

View File

@ -54,7 +54,7 @@ const createBackup = () => {
if (err) { if (err) {
error(err.message); error(err.message);
} }
logger('\x1b[32m%s\x1b[0m',stderr); logger('\x1b[32m%s\x1b[0m', stderr);
logger('\x1b[35m%s\x1b[0m', new Date().toLocaleString() logger('\x1b[35m%s\x1b[0m', new Date().toLocaleString()
+ ': cron job finished - CREATE BACKUP'); + ': cron job finished - CREATE BACKUP');
}) })

View File

@ -12,8 +12,16 @@ let check = (requiredPermission, actualPermission, res, next) => {
}; };
module.exports = { module.exports = {
checkSql: (req, res, next) => { check(1, req.user.permission, res, next) }, checkSql: (req, res, next) => {
checkHl: (req, res, next) => { check(2, req.user.permission, res, next) }, check(1, req.user.permission, res, next)
checkMT: (req, res, next) => { check(3, req.user.permission, res, next) }, },
checkAdmin: (req, res, next) => { check(4, req.user.permission, res, next) } checkHl: (req, res, next) => {
check(2, req.user.permission, res, next)
},
checkMT: (req, res, next) => {
check(3, req.user.permission, res, next)
},
checkAdmin: (req, res, next) => {
check(4, req.user.permission, res, next)
}
}; };

View File

@ -34,8 +34,8 @@ router.use((req, res, next) => {
// request type application/json check // request type application/json check
router.use((req, res, next) => { router.use((req, res, next) => {
if (['POST', 'PUT', 'PATCH'].indexOf(req.method) > -1 && if (['POST', 'PUT', 'PATCH'].indexOf(req.method) > -1 &&
(!( /multipart\/form-data/.test(req.get('Content-Type'))) && (!(/multipart\/form-data/.test(req.get('Content-Type'))) &&
!( /application\/json/.test(req.get('Content-Type'))))) { !(/application\/json/.test(req.get('Content-Type'))))) {
// send error code 415: unsupported media type // send error code 415: unsupported media type
res.status(415).send('wrong Content-Type'); // user has SEND the wrong type res.status(415).send('wrong Content-Type'); // user has SEND the wrong type
} else if (!req.accepts('json')) { } else if (!req.accepts('json')) {

View File

@ -36,6 +36,6 @@ const LogTransportSchema = new Schema({
collection: 'logTransport' collection: 'logTransport'
}); });
// optional more indices // optional more indices
LogTransportSchema.index({war: 1}); LogTransportSchema.index({war: 1, driver: 1});
module.exports = mongoose.model('LogTransport', LogTransportSchema); module.exports = mongoose.model('LogTransport', LogTransportSchema);

View File

@ -0,0 +1,34 @@
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const LogVehicleKillSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true
},
time: {
type: Date,
required: true
},
shooter: {
type: String
},
target: {
type: String,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR', 'NONE'],
required: true
}
}, {
collection: 'logVehicle'
});
// optional more indices
LogVehicleKillSchema.index({war: 1, shooter: 1, target: 1});
module.exports = mongoose.model('LogVehicle', LogVehicleKillSchema);

View File

@ -24,6 +24,12 @@ const PlayerSchema = new Schema({
set: v => Math.round(v), set: v => Math.round(v),
required: true required: true
}, },
vehicle: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
death: { death: {
type: Number, type: Number,
get: v => Math.round(v), get: v => Math.round(v),
@ -58,12 +64,17 @@ const PlayerSchema = new Schema({
type: Number, type: Number,
get: v => Math.round(v), get: v => Math.round(v),
set: v => Math.round(v) set: v => Math.round(v)
},
steamUUID: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v)
} }
}, { }, {
collection: 'player', collection: 'player',
timestamps: {createdAt: 'timestamp'} timestamps: {createdAt: 'timestamp'}
}); });
// optional more indices // optional more indices
PlayerSchema.index({timestamp: 1}); PlayerSchema.index({warId: 1});
module.exports = mongoose.model('Player', PlayerSchema); module.exports = mongoose.model('Player', PlayerSchema);

View File

@ -11,7 +11,7 @@ const WarSchema = new Schema({
date: { date: {
type: Date, type: Date,
}, },
endDate : { endDate: {
type: Date, type: Date,
}, },
ptBlufor: { ptBlufor: {

View File

@ -15,70 +15,72 @@ const AppUserModel = require('../models/app-user');
const account = express.Router(); const account = express.Router();
account.route('/') account.route('/')
.get((req, res, next) => { .get((req, res, next) => {
AppUserModel.find({}, {}, {sort: {username: 1}}).populate('squad').exec((err, items) => { AppUserModel.find({}, {}, {sort: {username: 1}}).populate('squad').exec((err, items) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
res.locals.items = items; res.locals.items = items;
res.locals.processed = true; res.locals.processed = true;
next(); next();
}) })
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
// routes ********************** // routes **********************
account.route('/:id') account.route('/:id')
.patch((req, res, next) => { .patch((req, res, next) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) { 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 // 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); 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; err.status = codes.notfound;
next(err); next(err);
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
// increment version manually as we do not use .save(.) // increment version manually as we do not use .save(.)
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
AppUserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}).populate('squad').exec((err, item) => { // reset attributes that are missing.
if (err) { AppUserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}).populate('squad').exec((err, item) => {
err.status = codes.wrongrequest; if (err) {
} err.status = codes.wrongrequest;
else if (!item) { }
err = new Error("appUser not found"); else if (!item) {
err.status = codes.notfound; err = new Error("appUser not found");
} err.status = codes.notfound;
else { }
res.locals.items = item; else {
} res.locals.items = item;
next(err); }
}) next(err);
}) })
})
.delete((req, res, next) => { .delete((req, res, next) => {
AppUserModel.findByIdAndRemove(req.params.id, (err, item) => { AppUserModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // 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; // user.use(..)
next(err); // this works because err is in normal case undefined and that is the same as no parameter res.locals.processed = true;
}); next(err); // this works because err is in normal case undefined and that is the same as no parameter
}) });
})
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -21,25 +21,25 @@ const authenticate = express.Router();
// routes ********************** // routes **********************
authenticate.route('/') authenticate.route('/')
.post((req, res, next) => { .post((req, res, next) => {
authCheck(req.body.username, req.body.password, res) authCheck(req.body.username, req.body.password, res)
.then((user) => { .then((user) => {
if (user) { if (user) {
// authentication successful // authentication successful
res.send(user); res.send(user);
} else { } else {
// authentication failed // authentication failed
res.status(codes.unauthorized).send('Username or password is incorrect'); res.status(codes.unauthorized).send('Username or password is incorrect');
} }
}) })
.catch((err) => { .catch((err) => {
res.status(codes.wrongrequest).send(err); res.status(codes.wrongrequest).send(err);
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
let authCheck = (username, password, res) => { let authCheck = (username, password, res) => {
const deferred = Q.defer(); const deferred = Q.defer();
@ -76,19 +76,19 @@ let authCheck = (username, password, res) => {
// ******************************** SIGNUP ************************ // ******************************** SIGNUP ************************
authenticate.route('/signup') authenticate.route('/signup')
.post((req, res, next) => { .post((req, res, next) => {
create(req.body) create(req.body)
.then(() => { .then(() => {
res.sendStatus(200); res.sendStatus(200);
}) })
.catch((err) => { .catch((err) => {
res.status(400).send(err); res.status(400).send(err);
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
let create = (userParam) => { let create = (userParam) => {
const deferred = Q.defer(); const deferred = Q.defer();

View File

@ -31,127 +31,129 @@ const awarding = express.Router();
// routes ********************** // routes **********************
awarding.route('/') awarding.route('/')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {}; const filter = {};
if (req.query.userId) { if (req.query.userId) {
filter.userId = req.query.userId; filter.userId = req.query.userId;
}
if (req.query.inProgress) {
filter.confirmed = 0;
}
if (req.query.simple) {
AwardingModel.find(filter, {}, {sort: {date: 'desc'}}, (err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
// with return before (or after) the next(err) we prevent that the code continues here after next(err) has finished.
// this saves an extra else {..}
}
// if the collection is empty we do not send empty arrays back.
if (items && items.length > 0) {
res.locals.items = items;
}
res.locals.processed = true;
next();
});
} else {
AwardingModel.find(filter, {}, {sort: {date: 'desc'}})
.populate('decorationId').populate('proposer', resultSet).populate('userId').exec((err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
// with return before (or after) the next(err) we prevent that the code continues here after next(err) has finished.
// this saves an extra else {..}
}
let results = [];
if (req.query.fractFilter) {
for (let item of items) {
if (item.decorationId.fraction === req.query.fractFilter) {
results.push(item)
}
} }
res.locals.items = results; if (req.query.inProgress) {
filter.confirmed = 0;
}
if (req.query.simple) {
AwardingModel.find(filter, {}, {sort: {date: 'desc'}}, (err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
// with return before (or after) the next(err) we prevent that the code continues here after next(err)
// has finished. this saves an extra else {..}
}
// if the collection is empty we do not send empty arrays back.
if (items && items.length > 0) {
res.locals.items = items;
}
res.locals.processed = true;
next();
});
} else {
AwardingModel.find(filter, {}, {sort: {date: 'desc'}})
.populate('decorationId').populate('proposer', resultSet).populate('userId').exec((err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
// with return before (or after) the next(err) we prevent that the code continues here after next(err)
// has finished. this saves an extra else {..}
}
let results = [];
if (req.query.fractFilter) {
for (let item of items) {
if (item.decorationId.fraction === req.query.fractFilter) {
results.push(item)
}
}
res.locals.items = results;
} else { } else {
res.locals.items = items; res.locals.items = items;
} }
res.locals.processed = true; res.locals.processed = true;
next(); next();
}); });
} }
}) })
.post(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .post(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
const award = new AwardingModel(req.body); const award = new AwardingModel(req.body);
award.confirmed = 1; award.confirmed = 1;
award.proposer = req.user._id; award.proposer = req.user._id;
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
award.save((err) => { award.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err); return next(err);
} }
res.status(codes.created); res.status(codes.created);
res.locals.items = award; res.locals.items = award;
next(); next();
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
awarding.route('/:id') awarding.route('/:id')
.patch(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) { 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 // 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); 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; err.status = codes.notfound;
next(err); next(err);
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
// optional task 3: increment version manually as we do not use .save(.) // optional task 3: increment version manually as we do not use .save(.)
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
AwardingModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { // reset attributes that are missing.
if (err) { AwardingModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
err.status = codes.wrongrequest; if (err) {
} err.status = codes.wrongrequest;
else if (!item) { }
err = new Error("item not found"); else if (!item) {
err.status = codes.notfound; err = new Error("item not found");
} err.status = codes.notfound;
else { }
res.locals.items = item; else {
} res.locals.items = item;
next(err); }
}) next(err);
}) })
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
AwardingModel.findByIdAndRemove(req.params.id, (err, item) => { AwardingModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // 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; // user.use(..)
next(err); // this works because err is in normal case undefined and that is the same as no parameter res.locals.processed = true;
}); next(err); // this works because err is in normal case undefined and that is the same as no parameter
}) });
})
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -23,63 +23,63 @@ const campaigns = express.Router();
// routes ********************** // routes **********************
campaigns.route('/') campaigns.route('/')
.post(apiAuthenticationMiddleware, checkMT, (req, res, next) => { .post(apiAuthenticationMiddleware, checkMT, (req, res, next) => {
const campaign = new CampaignModel(req.body); const campaign = new CampaignModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
campaign.save((err) => { campaign.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err); return next(err);
} }
res.status(codes.created); res.status(codes.created);
res.locals.items = campaign; res.locals.items = campaign;
next(); next();
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
campaigns.route('/:id') campaigns.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
CampaignModel.findById(req.params.id, (err, item) => { CampaignModel.findById(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
return next(err); return next(err);
} }
res.locals.items = item; res.locals.items = item;
return next(); return next();
}); });
}) })
.delete((req, res, next) => { .delete((req, res, next) => {
CampaignModel.findByIdAndRemove(req.params.id, (err, item) => { CampaignModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
return next(err); return next(err);
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
return next(err); return next(err);
} }
WarModel.find({campaign: req.params.id}).remove().exec(); WarModel.find({campaign: req.params.id}).remove().exec();
res.locals.processed = true; res.locals.processed = true;
next(); next();
}) })
}) })
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -14,23 +14,23 @@ const createSignature = require('../tools/signature-tool');
const command = express.Router(); const command = express.Router();
command.route('/createSignature') command.route('/createSignature')
.post((req, res, next) => { .post((req, res, next) => {
createAllSignatures(); createAllSignatures();
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
command.route('/createSignature/:id') command.route('/createSignature/:id')
.post((req, res, next) => { .post((req, res, next) => {
const userId = req.params.id; const userId = req.params.id;
createSignature(userId, res, next); createSignature(userId, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -26,136 +26,145 @@ const decoration = express.Router();
// routes ********************** // routes **********************
decoration.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, {}, {sort: {fraction: 'asc', isMedal: 'asc', sortingNumber: 'asc', name: 'asc'}}, (err, items) => { DecorationModel.find(filter, {}, {
if (err) { sort: {
err.status = codes.servererror; fraction: 'asc',
return next(err); isMedal: 'asc',
} sortingNumber: 'asc',
if (items && items.length > 0) { name: 'asc'
res.locals.items = items; }
} else { }, (err, items) => {
res.locals.items = []; if (err) {
} err.status = codes.servererror;
res.locals.processed = true; return next(err);
next(); }
}); if (items && items.length > 0) {
}) res.locals.items = items;
} else {
res.locals.items = [];
}
res.locals.processed = true;
next();
});
})
.post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
const decoration = new DecorationModel(req.body); const decoration = new DecorationModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
decoration.save((err) => { decoration.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err); return next(err);
} }
res.status(codes.created); res.status(codes.created);
res.locals.items = decoration; res.locals.items = decoration;
fs.appendFile(__dirname + '/../resource/decoration/' + decoration.id + '.png', new Buffer(req.file.buffer), fs.appendFile(__dirname + '/../resource/decoration/' + decoration.id + '.png', new Buffer(req.file.buffer),
(err) => { (err) => {
if (err) next(err); if (err) next(err);
}); });
next(); next();
}) })
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
decoration.route('/:id') decoration.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
DecorationModel.findById(req.params.id, (err, item) => { DecorationModel.findById(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
return next(err); return next(err);
} }
res.locals.items = item; res.locals.items = item;
next(); next();
}); });
}) })
.patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) { 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 // 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); 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; err.status = codes.notfound;
next(err); next(err);
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
// optional task 3: increment version manually as we do not use .save(.) // optional task 3: increment version manually as we do not use .save(.)
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
if (req.file) { if (req.file) {
const file = __dirname + '/../resource/decoration/' + req.body._id + '.png'; const file = __dirname + '/../resource/decoration/' + req.body._id + '.png';
fs.unlink(file, (err) => { fs.unlink(file, (err) => {
if (err) next(err); if (err) next(err);
fs.appendFile(file, new Buffer(req.file.buffer), (err) => { fs.appendFile(file, new Buffer(req.file.buffer), (err) => {
if (err) next(err); if (err) next(err);
}); });
}); });
} }
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need
DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { // to reset attributes that are missing.
if (err) { DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
err.status = codes.wrongrequest; if (err) {
} err.status = codes.wrongrequest;
else if (!item) { }
err = new Error("item not found"); else if (!item) {
err.status = codes.notfound; err = new Error("item not found");
} err.status = codes.notfound;
else { }
res.locals.items = item; else {
} res.locals.items = item;
next(err); }
}) next(err);
}) })
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
DecorationModel.findByIdAndRemove(req.params.id, (err, item) => { DecorationModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// deleted all awardings linked to this decoration // deleted all awardings linked to this decoration
AwardingsModel.find({decorationId: req.params.id}).remove().exec(); AwardingsModel.find({decorationId: req.params.id}).remove().exec();
// delete graphic // delete graphic
fs.unlink(__dirname + '/../resource/decoration/' + req.params.id + '.png', fs.unlink(__dirname + '/../resource/decoration/' + req.params.id + '.png',
(err) => { (err) => {
if (err) next(err); if (err) next(err);
}); });
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // 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; // user.use(..)
next(err); // this works because err is in normal case undefined and that is the same as no parameter res.locals.processed = true;
}); next(err); // this works because err is in normal case undefined and that is the same as no parameter
}) });
})
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -13,6 +13,7 @@ const LogBudgetModel = require('../models/logs/budget');
const LogRespawnModel = require('../models/logs/respawn'); const LogRespawnModel = require('../models/logs/respawn');
const LogReviveModel = require('../models/logs/revive'); const LogReviveModel = require('../models/logs/revive');
const LogKillModel = require('../models/logs/kill'); const LogKillModel = require('../models/logs/kill');
const LogVehicleModel = require('../models/logs/vehicle');
const LogTransportModel = require('../models/logs/transport'); const LogTransportModel = require('../models/logs/transport');
const LogFlagModel = require('../models/logs/flag'); const LogFlagModel = require('../models/logs/flag');
const LogPointsModel = require('../models/logs/points'); const LogPointsModel = require('../models/logs/points');
@ -34,122 +35,124 @@ function processLogRequest(model, filter, res, next) {
// routes ********************** // routes **********************
logsRouter.route('/:warId') logsRouter.route('/:warId')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
const sort = {sort: {time: 1}}; const sort = {sort: {time: 1}};
const pointsObjects = LogPointsModel.find(filter, {}, sort); const pointsObjects = LogPointsModel.find(filter, {}, sort);
const budgetObjects = LogBudgetModel.find(filter, {}, sort); const budgetObjects = LogBudgetModel.find(filter, {}, sort);
const respawnObjects = LogRespawnModel.find(filter, {}, sort); const respawnObjects = LogRespawnModel.find(filter, {}, sort);
const reviveObjects = LogReviveModel.find(filter, {}, sort); const reviveObjects = LogReviveModel.find(filter, {}, sort);
const killObjects = LogKillModel.find(filter, {}, sort); const killObjects = LogKillModel.find(filter, {}, sort);
const transportObjects = LogTransportModel.find(filter, {}, sort); const vehicleObjects = LogVehicleModel.find(filter, {}, sort);
const flagObjects = LogFlagModel.find(filter, {}, sort); const transportObjects = LogTransportModel.find(filter, {}, sort);
const resources = { const flagObjects = LogFlagModel.find(filter, {}, sort);
points: pointsObjects.exec.bind(pointsObjects), const resources = {
budget: budgetObjects.exec.bind(budgetObjects), points: pointsObjects.exec.bind(pointsObjects),
respawn: respawnObjects.exec.bind(respawnObjects), budget: budgetObjects.exec.bind(budgetObjects),
revive: reviveObjects.exec.bind(reviveObjects), respawn: respawnObjects.exec.bind(respawnObjects),
kill: killObjects.exec.bind(killObjects), revive: reviveObjects.exec.bind(reviveObjects),
transport: transportObjects.exec.bind(transportObjects), kill: killObjects.exec.bind(killObjects),
flag: flagObjects.exec.bind(flagObjects) vehicle: killObjects.exec.bind(vehicleObjects),
}; transport: transportObjects.exec.bind(transportObjects),
flag: flagObjects.exec.bind(flagObjects)
};
async.parallel(resources, function (error, results){ async.parallel(resources, function (error, results) {
if (error) { if (error) {
res.status(500).send(error); res.status(500).send(error);
return; return;
} }
res.locals.items = results; res.locals.items = results;
next(); next();
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.route('/:warId/budget') logsRouter.route('/:warId/budget')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
if (req.query.fraction) filter['fraction'] = req.query.fraction; if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogBudgetModel, filter, res, next); processLogRequest(LogBudgetModel, filter, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.route('/:warId/respawn') logsRouter.route('/:warId/respawn')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
if (req.query.player) filter['player'] = req.query.player; if (req.query.player) filter['player'] = req.query.player;
processLogRequest(LogRespawnModel, filter, res, next); processLogRequest(LogRespawnModel, filter, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.route('/:warId/revive') logsRouter.route('/:warId/revive')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
if (req.query.medic) filter['medic'] = req.query.medic; if (req.query.medic) filter['medic'] = req.query.medic;
if (req.query.patient) filter['patient'] = req.query.patient; if (req.query.patient) filter['patient'] = req.query.patient;
if (req.query.stabilized) filter['stabilized'] = true; if (req.query.stabilized) filter['stabilized'] = true;
if (req.query.revive) filter['stabilized'] = false; if (req.query.revive) filter['stabilized'] = false;
if (req.query.fraction) filter['fraction'] = req.query.fraction; if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogReviveModel, filter, res, next); processLogRequest(LogReviveModel, filter, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.route('/:warId/kills') logsRouter.route('/:warId/kills')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
if (req.query.shooter) filter['shooter'] = req.query.shooter; if (req.query.shooter) filter['shooter'] = req.query.shooter;
if (req.query.target) filter['target'] = req.query.target; if (req.query.target) filter['target'] = req.query.target;
if (req.query.friendlyFire) filter['friendlyFire'] = true; if (req.query.friendlyFire) filter['friendlyFire'] = true;
if (req.query.noFriendlyFire) filter['friendlyFire'] = false; if (req.query.noFriendlyFire) filter['friendlyFire'] = false;
if (req.query.fraction) filter['fraction'] = req.query.fraction; if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogKillModel, filter, res, next); processLogRequest(LogKillModel, filter, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.route('/:warId/transport') logsRouter.route('/:warId/transport')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
if (req.query.driver) filter['driver'] = req.query.driver; if (req.query.driver) filter['driver'] = req.query.driver;
if (req.query.passenger) filter['passenger'] = req.query.passenger; if (req.query.passenger) filter['passenger'] = req.query.passenger;
if (req.query.fraction) filter['fraction'] = req.query.fraction; if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogTransportModel, filter, res, next); processLogRequest(LogTransportModel, filter, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.route('/:warId/flag') logsRouter.route('/:warId/flag')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
if (req.query.player) filter['player'] = req.query.player; if (req.query.player) filter['player'] = req.query.player;
if (req.query.capture) filter['capture'] = true; if (req.query.capture) filter['capture'] = true;
if (req.query.defend) filter['capture'] = false; if (req.query.defend) filter['capture'] = false;
if (req.query.fraction) filter['fraction'] = req.query.fraction; if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogFlagModel, filter, res, next); processLogRequest(LogFlagModel, filter, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.route('/:warId/points') logsRouter.route('/:warId/points')
.get((req, res, next) => { .get((req, res, next) => {
const filter = {war: req.params.warId}; const filter = {war: req.params.warId};
if (req.query.fraction) filter['fraction'] = req.query.fraction; if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogPointsModel, filter, res, next); processLogRequest(LogPointsModel, filter, res, next);
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
logsRouter.use(routerHandling.emptyResponse); logsRouter.use(routerHandling.emptyResponse);

View File

@ -18,93 +18,93 @@ const overview = express.Router();
// routes ********************** // routes **********************
overview.route('/') overview.route('/')
.get((req, res, next) => { .get((req, res, next) => {
let countOpfor = 0; let countOpfor = 0;
let countBlufor = 0; let countBlufor = 0;
const armyOverview = { const armyOverview = {
BLUFOR: { BLUFOR: {
squads: [] squads: []
}, },
OPFOR: { OPFOR: {
squads: [] squads: []
} }
}; };
SquadModel.find({}, {'sortingNumber': 0, 'updatedAt': 0, 'timestamp': 0, '__v': 0}, { SquadModel.find({}, {'sortingNumber': 0, 'updatedAt': 0, 'timestamp': 0, '__v': 0}, {
sort: { sort: {
sortingNumber: 'asc', sortingNumber: 'asc',
name: 'asc' name: 'asc'
} }
}, (err, squads) => { }, (err, squads) => {
if (err) {
return next(err);
}
async.eachSeries(squads, (squad, callback) => {
UserModel.find({squadId: squad._id}, {
'squadId': 0,
'updatedAt': 0,
'timestamp': 0,
'__v': 0
}, {sort: {rankLvl: 'desc', name: 'asc'}}, (err, users) => {
const squadMembers = [];
async.eachSeries(users, (user, callback) => {
const usr = user.toObject();
RankModel.findOne({level: user.rankLvl, fraction: squad.fraction}, (err, rank) => {
if (err) {
return next(err);
}
// not defined if rank was deleted / rankLvl not available for fraction
if (rank) {
usr.rank = rank.name;
}
delete usr.rankLvl;
squadMembers.push(usr)
callback();
});
}, (err) => {
if (err) { if (err) {
return next(err); return next(err);
} }
async.eachSeries(squads, (squad, callback) => {
UserModel.find({squadId: squad._id}, {
'squadId': 0,
'updatedAt': 0,
'timestamp': 0,
'__v': 0
}, {sort: {rankLvl: 'desc', name: 'asc'}}, (err, users) => {
const squadMembers = [];
async.eachSeries(users, (user, callback) => {
const usr = user.toObject();
RankModel.findOne({level: user.rankLvl, fraction: squad.fraction}, (err, rank) => {
if (err) {
return next(err);
}
// do not return empty squads // not defined if rank was deleted / rankLvl not available for fraction
if (squadMembers.length > 0) { if (rank) {
const s = squad.toObject(); usr.rank = rank.name;
s.members = squadMembers; }
s.memberCount = squadMembers.length; delete usr.rankLvl;
if (s.fraction === 'BLUFOR') { squadMembers.push(usr)
delete s.fraction;
armyOverview.BLUFOR.squads.push(s);
countBlufor += s.members.length;
}
if (s.fraction === 'OPFOR') {
delete s.fraction;
armyOverview.OPFOR.squads.push(s);
countOpfor += s.members.length;
}
}
callback(); callback();
});
}, (err) => {
if (err) {
return next(err);
}
// do not return empty squads
if (squadMembers.length > 0) {
const s = squad.toObject();
s.members = squadMembers;
s.memberCount = squadMembers.length;
if (s.fraction === 'BLUFOR') {
delete s.fraction;
armyOverview.BLUFOR.squads.push(s);
countBlufor += s.members.length;
}
if (s.fraction === 'OPFOR') {
delete s.fraction;
armyOverview.OPFOR.squads.push(s);
countOpfor += s.members.length;
}
}
callback();
});
});
}, (err) => {
if (err) {
return next(err);
}
armyOverview.BLUFOR.memberCount = countBlufor;
armyOverview.OPFOR.memberCount = countOpfor;
res.locals.items = armyOverview;
res.locals.processed = true;
next();
});
}); });
}); })
}, (err) => { .all(
if (err) { routerHandling.httpMethodNotAllowed
return next(err); );
}
armyOverview.BLUFOR.memberCount = countBlufor;
armyOverview.OPFOR.memberCount = countOpfor;
res.locals.items = armyOverview;
res.locals.processed = true;
next();
});
});
})
.all(
routerHandling.httpMethodNotAllowed
);
overview.use(routerHandling.emptyResponse); overview.use(routerHandling.emptyResponse);

View File

@ -14,99 +14,125 @@ const CampaignModel = require('../models/campaign');
const PlayerModel = require('../models/player'); const PlayerModel = require('../models/player');
const WarModel = require('../models/war'); const WarModel = require('../models/war');
// Util
const isSteamUUID = require('../tools/util').isSteamUUID;
const campaignPlayer = express.Router(); const campaignPlayer = express.Router();
// routes ********************** // routes **********************
campaignPlayer.route('/ranking/:campaignId') campaignPlayer.route('/ranking/:campaignId')
.get((req, res, next) => { .get((req, res, next) => {
WarModel.find({campaign: req.params.campaignId}, '_id', (err, wars) => { WarModel.find({campaign: req.params.campaignId}, '_id', (err, wars) => {
if (err) return next(err); if (err) return next(err);
const warIds = wars.map((obj) => { const warIds = wars.map((obj) => {
return obj._id; return obj._id;
}); });
PlayerModel.find({warId: {"$in": warIds}}, (err, items) => { PlayerModel.find({warId: {"$in": warIds}}, (err, items) => {
if (err) return next(err); if (err) return next(err);
if (!items || items.length === 0) { if (!items || items.length === 0) {
const err = new Error('No players for given campaignId'); const err = new Error('No players for given campaignId');
err.status = codes.notfound; err.status = codes.notfound;
return next(err) return next(err)
} }
const rankingItems = []; const rankingItems = [];
new Set(items.map(x => x.name)).forEach(playerName => {
const playerInstances = items.filter(p => p.name === playerName);
const resItem = {name: playerName, kill: 0, death: 0, friendlyFire: 0, revive: 0, respawn: 0, flagTouch: 0};
for (let i = 0; i < playerInstances.length; i++) {
resItem.kill += playerInstances[i].kill;
resItem.death += playerInstances[i].death;
resItem.friendlyFire += playerInstances[i].friendlyFire;
resItem.revive += playerInstances[i].revive;
resItem.respawn += playerInstances[i].respawn;
resItem.flagTouch += playerInstances[i].flagTouch;
}
resItem.fraction = playerInstances[playerInstances.length - 1].fraction;
rankingItems.push(resItem);
});
function getSortedField(fieldName) { // check only first player to have valid steamUUID - then decide if tracked by name or by ID
let num = 1; const usesUUID = isSteamUUID(items[0].steamUUID);
rankingItems.sort((a, b) => b[fieldName] - a[fieldName])
const res = JSON.parse(JSON.stringify(rankingItems));
for (const entity of res) {
entity.num = num++;
}
return res;
}
res.locals.items = { new Set(items.map(usesUUID ? x => x.steamUUID : x => x.name))
kill: getSortedField('kill'), .forEach(player => {
death: getSortedField('death'), const playerInstances = items.filter(usesUUID ? p => p.steamUUID === player : p => p.name === player);
friendlyFire: getSortedField('friendlyFire'), const resItem = {
revive: getSortedField('revive'), name: usesUUID ? playerInstances[playerInstances.length - 1].name : player,
respawn: getSortedField('respawn'), kill: 0,
flagTouch: getSortedField('flagTouch') vehicle: 0,
}; death: 0,
next(); friendlyFire: 0,
}) revive: 0,
}) respawn: 0,
}) flagTouch: 0
};
for (let i = 0; i < playerInstances.length; i++) {
resItem.kill += playerInstances[i].kill;
resItem.death += playerInstances[i].death;
resItem.friendlyFire += playerInstances[i].friendlyFire;
resItem.vehicle += playerInstances[i].vehicle;
resItem.revive += playerInstances[i].revive;
resItem.respawn += playerInstances[i].respawn;
resItem.flagTouch += playerInstances[i].flagTouch;
}
resItem.fraction = playerInstances[playerInstances.length - 1].fraction;
rankingItems.push(resItem);
});
.all( function getSortedField(fieldName) {
routerHandling.httpMethodNotAllowed let num = 1;
); rankingItems.sort((a, b) => b[fieldName] - a[fieldName])
const res = JSON.parse(JSON.stringify(rankingItems));
for (const entity of res) {
entity.num = num++;
}
return res;
}
campaignPlayer.route('/single/:campaignId/:playerName') res.locals.items = {
.get((req, res, next) => { kill: getSortedField('kill'),
CampaignModel.findById(req.params.campaignId, (err, campaign) => { death: getSortedField('death'),
if (err) return next(err); friendlyFire: getSortedField('friendlyFire'),
WarModel.find({campaign: req.params.campaignId}, '_id', (err, wars) => { vehicle: getSortedField('vehicle'),
if (err) return next(err); revive: getSortedField('revive'),
const warIds = wars.map((obj) => { respawn: getSortedField('respawn'),
return obj._id; flagTouch: getSortedField('flagTouch')
}); };
PlayerModel.find({name: req.params.playerName, warId: {"$in": warIds}}) next();
.populate('warId') })
.exec((err, items) => { })
if (err) return next(err); })
if (!items || items.length === 0) {
const err = new Error('Unknown player name');
err.status = codes.notfound;
return next(err)
}
res.locals.items = {
name: req.params.playerName,
campaign: campaign,
players: items
};
next();
})
})
})
})
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
campaignPlayer.route('/single/:campaignId/:playerId')
.get((req, res, next) => {
CampaignModel.findById(req.params.campaignId, (err, campaign) => {
if (err) return next(err);
WarModel.find({campaign: req.params.campaignId}, '_id', (err, wars) => {
if (err) return next(err);
const warIds = wars.map((obj) => {
return obj._id;
});
// find by player name until v1.6.12, afterwards by SteamUUID
const playerId = req.params.playerId;
const filter = {};
filter[isSteamUUID(playerId) ? 'steamUUID' : 'name'] = playerId;
filter['warId'] = {"$in": warIds};
PlayerModel.find(filter)
.populate('warId')
.exec((err, items) => {
if (err) return next(err);
if (!items || items.length === 0) {
const err = new Error('Unknown player id');
err.status = codes.notfound;
return next(err)
}
res.locals.items = {
name: items[items.length - 1].name,
campaign: campaign,
players: items
};
next();
})
})
})
})
.all(
routerHandling.httpMethodNotAllowed
);
campaignPlayer.use(routerHandling.emptyResponse); campaignPlayer.use(routerHandling.emptyResponse);

View File

@ -24,130 +24,132 @@ const ranks = express.Router();
// routes ********************** // routes **********************
ranks.route('/') ranks.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'}
} }
RankModel.find(filter, {}, {sort: {fraction: 'asc', level: 'asc'}}, (err, items) => { RankModel.find(filter, {}, {sort: {fraction: 'asc', level: 'asc'}}, (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) => {
const rank = new RankModel(req.body); const rank = new RankModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
rank.save((err) => { rank.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
next(err); next(err);
} }
res.status(codes.created); res.status(codes.created);
res.locals.items = rank; res.locals.items = rank;
fs.appendFile(__dirname + '/../resource/rank/' + rank.id + '.png', new Buffer(req.file.buffer), fs.appendFile(__dirname + '/../resource/rank/' + rank.id + '.png', new Buffer(req.file.buffer),
(err) => { (err) => {
if (err) next(err); if (err) next(err);
}); });
next(); next();
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
ranks.route('/:id') ranks.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
RankModel.findById(req.params.id, (err, item) => { RankModel.findById(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
return next(err); return next(err);
} }
res.locals.items = item; res.locals.items = item;
next(); next();
}); });
}) })
.patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) { 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 // 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); 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; err.status = codes.notfound;
next(err); next(err);
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
// optional task 3: increment version manually as we do not use .save(.) // optional task 3: increment version manually as we do not use .save(.)
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
if (req.file) { if (req.file) {
const file = __dirname + '/../resource/rank/' + req.body._id + '.png'; const file = __dirname + '/../resource/rank/' + req.body._id + '.png';
fs.unlink(file, (err) => { fs.unlink(file, (err) => {
if (err) next(err); if (err) next(err);
fs.appendFile(file, new Buffer(req.file.buffer), fs.appendFile(file, new Buffer(req.file.buffer),
(err) => { (err) => {
if (err) next(err); if (err) next(err);
}); });
}); });
} }
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
RankModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { // reset attributes that are missing.
if (err) { RankModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
err.status = codes.wrongrequest; if (err) {
} err.status = codes.wrongrequest;
else if (!item) { }
err = new Error("item not found"); else if (!item) {
err.status = codes.notfound; err = new Error("item not found");
} err.status = codes.notfound;
else { }
res.locals.items = item; else {
} res.locals.items = item;
next(err); }
}) next(err);
}) })
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
RankModel.findByIdAndRemove(req.params.id, (err, item) => { RankModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // 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; // user.use(..)
next(err); // this works because err is in normal case undefined and that is the same as no parameter res.locals.processed = true;
}) next(err); // this works because err is in normal case undefined and that is the same as no parameter
}) })
})
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -35,145 +35,146 @@ const request = express.Router();
// routes ********************** // routes **********************
request.route('/award') request.route('/award')
.post(apiAuthenticationMiddleware, checkSql, (req, res, next) => { .post(apiAuthenticationMiddleware, checkSql, (req, res, next) => {
const award = new AwardingModel(req.body); const award = new AwardingModel(req.body);
award.confirmed = 0; award.confirmed = 0;
award.proposer = req.user._id; award.proposer = req.user._id;
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
award.save((err) => { award.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err); return next(err);
} }
res.status(codes.created); res.status(codes.created);
res.locals.items = award; res.locals.items = award;
next(); next();
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
request.route('/promotion') request.route('/promotion')
.get((req, res, next) => { .get((req, res, next) => {
// TODO: add SQL authentication // TODO: add SQL authentication
const squadFilter = req.query.squadId; const squadFilter = req.query.squadId;
const fractFilter = req.query.fractFilter; const fractFilter = req.query.fractFilter;
const progressFilter = req.query.inProgress; const progressFilter = req.query.inProgress;
let filter; let filter;
if (squadFilter) { if (squadFilter) {
filter = {squadId: squadFilter}; filter = {squadId: squadFilter};
} }
let userIds = []; let userIds = [];
UserModel.find(filter).populate('squadId').exec((err, items) => { UserModel.find(filter).populate('squadId').exec((err, items) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
for (let item of items) { for (let item of items) {
if (!fractFilter || (fractFilter && item.squadId && item.squadId.fraction === fractFilter)) { if (!fractFilter || (fractFilter && item.squadId && item.squadId.fraction === fractFilter)) {
userIds.push(item._id); userIds.push(item._id);
} }
} }
let promotionFilter = { let promotionFilter = {
userId: {"$in": userIds} userId: {"$in": userIds}
}; };
if (progressFilter) { if (progressFilter) {
promotionFilter.confirmed = 0; promotionFilter.confirmed = 0;
} }
PromotionModel.find(promotionFilter, {}, {sort: {timestamp: 'desc'}}) PromotionModel.find(promotionFilter, {}, {sort: {timestamp: 'desc'}})
.populate('userId').populate('proposer', resultSet).exec((err, items) => { .populate('userId').populate('proposer', resultSet).exec((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, checkSql, (req, res, next) => { .post(apiAuthenticationMiddleware, checkSql, (req, res, next) => {
const promotion = new PromotionModel(req.body); const promotion = new PromotionModel(req.body);
promotion.confirmed = 0; promotion.confirmed = 0;
promotion.proposer = req.user._id; promotion.proposer = req.user._id;
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
promotion.save((err) => { promotion.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err); return next(err);
} }
res.status(codes.created); res.status(codes.created);
res.locals.items = promotion; res.locals.items = promotion;
next(); next();
}); });
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
request.route('/promotion/:id') request.route('/promotion/:id')
.patch(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) { 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 // 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); 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; err.status = codes.notfound;
next(err); next(err);
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
PromotionModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { // reset attributes that are missing.
if (err) { PromotionModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
err.status = codes.wrongrequest; if (err) {
} err.status = codes.wrongrequest;
else if (!item) { }
err = new Error("item not found"); else if (!item) {
err.status = codes.notfound; err = new Error("item not found");
} err.status = codes.notfound;
else { }
if (item.confirmed === 1) { else {
let updateUser = { if (item.confirmed === 1) {
_id: item.userId, let updateUser = {
rankLvl: item.newRankLvl _id: item.userId,
}; rankLvl: item.newRankLvl
UserModel.findByIdAndUpdate(updateUser._id, updateUser, {new: true}, (err, item) => { };
if (err) { UserModel.findByIdAndUpdate(updateUser._id, updateUser, {new: true}, (err, item) => {
err.status = codes.wrongrequest; if (err) {
} err.status = codes.wrongrequest;
else if (!item) { }
err = new Error("user not found"); else if (!item) {
err.status = codes.notfound; err = new Error("user not found");
} err.status = codes.notfound;
}); }
} });
res.locals.items = item; }
} res.locals.items = item;
next(err); }
}) next(err);
}) })
.all( })
routerHandling.httpMethodNotAllowed .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

View File

@ -16,38 +16,38 @@ const signatures = express.Router();
// routes ********************** // routes **********************
signatures.route('/:id') signatures.route('/:id')
// does not use idValidator since it works by username // does not use idValidator since it works by username
.get((req, res, next) => { .get((req, res, next) => {
// decode UTF8-escape sequences (special characters) // decode UTF8-escape sequences (special characters)
const uri = decodeURIComponent(req.params.id); const uri = decodeURIComponent(req.params.id);
UserModel.findOne({username: uri}, (err, user) => { UserModel.findOne({username: uri}, (err, user) => {
const emptyFile = 'resource/signature/0.png'; const emptyFile = 'resource/signature/0.png';
if (user === null) { if (user === null) {
res.sendFile(emptyFile, {root: __dirname + '/../'}); res.sendFile(emptyFile, {root: __dirname + '/../'});
} else { } else {
const file = 'resource/signature/' + user._id + '.png'; const file = 'resource/signature/' + user._id + '.png';
fs.stat(__dirname + '/../' + file, (err, stat) => { fs.stat(__dirname + '/../' + file, (err, stat) => {
if (err === null) { if (err === null) {
res.sendFile(file, {root: __dirname + '/../'}); res.sendFile(file, {root: __dirname + '/../'});
} else if (err.code === 'ENOENT') { } else if (err.code === 'ENOENT') {
res.sendFile(emptyFile, {root: __dirname + '/../'}); res.sendFile(emptyFile, {root: __dirname + '/../'});
} else { } else {
err = new Error("Internal server error"); err = new Error("Internal server error");
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
}); });
} }
}) })
}) })
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -24,138 +24,140 @@ const squads = express.Router();
// routes ********************** // routes **********************
squads.route('/') squads.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) {
filter.name = {$regex: req.query.q, $options: 'i'}
}
SquadModel.find(filter, {}, {sort: {fraction: 'asc', sortingNumber: 'asc'}}, (err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
if (items) {
res.locals.items = items;
} else {
res.locals.items = [];
}
res.locals.processed = true;
next();
});
})
.post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
const squad = new SquadModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation
if (req.file) {
squad.save((err) => {
if (err) {
err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err);
} }
res.status(codes.created); if (req.query.q) {
res.locals.items = squad; filter.name = {$regex: req.query.q, $options: 'i'}
fs.appendFile(__dirname + '/../resource/squad/' + squad.id + '.png', new Buffer(req.file.buffer), (err) => { }
if (err) next(err); SquadModel.find(filter, {}, {sort: {fraction: 'asc', sortingNumber: 'asc'}}, (err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
if (items) {
res.locals.items = items;
} else {
res.locals.items = [];
}
res.locals.processed = true;
next();
}); });
next();
}) })
} else {
const err = new Error('no image file provided');
err.status = codes.wrongmediasend;
next(err);
}
})
.all( .post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
routerHandling.httpMethodNotAllowed const squad = new SquadModel(req.body);
); // timestamp and default are set automatically by Mongoose Schema Validation
if (req.file) {
squad.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 = squad;
fs.appendFile(__dirname + '/../resource/squad/' + squad.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(
routerHandling.httpMethodNotAllowed
);
squads.route('/:id') squads.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
SquadModel.findById(req.params.id, (err, item) => { SquadModel.findById(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
return next(err); return next(err);
} }
res.locals.items = item; res.locals.items = item;
next(); next();
});
})
.patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
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.
}
// increment version manually as we do not use .save(.)
req.body.updatedAt = new Date();
req.body.$inc = {__v: 1};
if (req.file) {
const file = __dirname + '/../resource/squad/' + req.body._id + '.png';
fs.unlink(file, (err) => {
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 need to reset attributes that are missing. .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {
SquadModel.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);
})
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => { if (!req.body || (req.body._id && req.body._id !== req.params.id)) {
SquadModel.findByIdAndRemove(req.params.id, (err, item) => { // little bit different as in PUT. :id does not need to be in data, but if the _id and url id must match
if (err) { const err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + " " + req.body._id);
err.status = codes.wrongrequest; err.status = codes.notfound;
} next(err);
else if (!item) { return; // prevent node to process this function further after next() has finished.
err = new Error("item not found"); }
err.status = codes.notfound;
}
// delete graphic // increment version manually as we do not use .save(.)
fs.unlink(__dirname + '/../resource/squad/' + req.params.id + '.png', (err) => { req.body.updatedAt = new Date();
if (err) next(err); req.body.$inc = {__v: 1};
});
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) if (req.file) {
res.locals.processed = true; const file = __dirname + '/../resource/squad/' + req.body._id + '.png';
next(err); // this works because err is in normal case undefined and that is the same as no parameter fs.unlink(file, (err) => {
}); if (err) next(err);
}) fs.appendFile(file, new Buffer(req.file.buffer), (err) => {
if (err) next(err);
});
});
}
.all( // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
routerHandling.httpMethodNotAllowed // reset attributes that are missing.
); SquadModel.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);
})
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
SquadModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) {
err.status = codes.wrongrequest;
}
else if (!item) {
err = new Error("item not found");
err.status = codes.notfound;
}
// delete graphic
fs.unlink(__dirname + '/../resource/squad/' + req.params.id + '.png', (err) => {
if (err) next(err);
});
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter
});
})
.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

View File

@ -28,212 +28,215 @@ users.get('/', offsetlimitMiddleware);
// routes ********************** // routes **********************
users.route('/') users.route('/')
.get((req, res, next) => { .get((req, res, next) => {
const userQuery = () => { const userQuery = () => {
UserModel.find(dbFilter, res.locals.filter, res.locals.limitskip) UserModel.find(dbFilter, res.locals.filter, res.locals.limitskip)
.populate('squadId') .populate('squadId')
.collation({locale: "en", strength: 2}) // case insensitive order .collation({locale: "en", strength: 2}) // case insensitive order
.sort('username').exec((err, users) => { .sort('username').exec((err, users) => {
if (err) return next(err); if (err) return next(err);
if (users.length === 0) { if (users.length === 0) {
res.locals.items = users; res.locals.items = users;
res.locals.processed = true; res.locals.processed = true;
return next(); return next();
} }
UserModel.count(dbFilter, (err, totalCount) => { UserModel.count(dbFilter, (err, totalCount) => {
res.set('x-total-count', totalCount); res.set('x-total-count', totalCount);
res.locals.items = users; res.locals.items = users;
res.locals.processed = true; res.locals.processed = true;
return next(); return next();
}) })
}) })
}; };
if (!req.query.q) req.query.q = ''; if (!req.query.q) req.query.q = '';
const dbFilter = {username: {"$regex": req.query.q, "$options": "i"}}; const dbFilter = {username: {"$regex": req.query.q, "$options": "i"}};
if (req.query.squadId) dbFilter["squadId"] = {"$eq": req.query.squadId}; if (req.query.squadId) dbFilter["squadId"] = {"$eq": req.query.squadId};
// squad / fraction filter setup // squad / fraction filter setup
if (req.query.fractFilter && req.query.fractFilter !== 'UNASSIGNED' && !req.query.squadId) { if (req.query.fractFilter && req.query.fractFilter !== 'UNASSIGNED' && !req.query.squadId) {
SquadModel.find({'fraction': req.query.fractFilter}, {_id: 1}, (err, squads) => { SquadModel.find({'fraction': req.query.fractFilter}, {_id: 1}, (err, squads) => {
dbFilter['squadId'] = {$in: squads.map(squad => squad.id)}; dbFilter['squadId'] = {$in: squads.map(squad => squad.id)};
userQuery(); userQuery();
}) })
} else { } else {
if (req.query.fractFilter === 'UNASSIGNED') { if (req.query.fractFilter === 'UNASSIGNED') {
dbFilter['squadId'] = {$eq: null}; dbFilter['squadId'] = {$eq: null};
} }
userQuery(); userQuery();
} }
}) })
.post(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .post(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
const user = new UserModel(req.body); const user = new UserModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
user.save((err) => { user.save((err) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
return next(err); return next(err);
} }
res.status(codes.created); res.status(codes.created);
UserModel.populate(user, {path: 'squadId'}, (err, extUser) => { UserModel.populate(user, {path: 'squadId'}, (err, extUser) => {
res.locals.items = extUser; res.locals.items = extUser;
res.locals.processed = true; res.locals.processed = true;
return next(); return next();
}) })
}); });
}) })
.all(routerHandling.httpMethodNotAllowed); .all(routerHandling.httpMethodNotAllowed);
users.route('/:id') users.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
UserModel.findById(req.params.id).populate('squadId').exec((err, user) => { UserModel.findById(req.params.id).populate('squadId').exec((err, user) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
} }
else if (!user) { else if (!user) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
return next(err); return next(err);
} }
res.locals.items = user; res.locals.items = user;
res.locals.processed = true; res.locals.processed = true;
return next(); return next();
}); });
}) })
.patch(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) { 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 // 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); 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; err.status = codes.notfound;
next(err); next(err);
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
// optional task 3: increment version manually as we do not use .save(.) // optional task 3: increment version manually as we do not use .save(.)
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
UserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { // reset attributes that are missing.
if (err) { UserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
err.status = codes.wrongrequest; if (err) {
} err.status = codes.wrongrequest;
else if (!item) { }
err = new Error("item not found"); else if (!item) {
err.status = codes.notfound; err = new Error("item not found");
} err.status = codes.notfound;
UserModel.populate(item, {path: 'squadId'}, (err, extUser) => { }
if (err) { UserModel.populate(item, {path: 'squadId'}, (err, extUser) => {
err.status = codes.servererror; if (err) {
return next(err); err.status = codes.servererror;
} return next(err);
if (!user) { }
res.locals.items = {}; if (!user) {
res.locals.processed = true; res.locals.items = {};
return next(); res.locals.processed = true;
} return next();
res.locals.items = extUser; }
res.locals.processed = true; res.locals.items = extUser;
return next(); res.locals.processed = true;
}) return next();
}) })
}) })
})
.put(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .put(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
// first check that the given element id is the same as the URL id // first check that the given element id is the same as the URL id
if (!req.body || req.body._id !== req.params.id) { if (!req.body || req.body._id !== req.params.id) {
// the URL does not fit the given element // the URL does not fit the given element
var err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + " " + req.body._id); var err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + " " + req.body._id);
err.status = codes.notfound; err.status = codes.notfound;
next(err); next(err);
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
// main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema // main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema
const user = new UserModel(req.body); const user = new UserModel(req.body);
UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) { UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) {
// with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the old one. // with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the
if (err) { // old one.
err.status = codes.wrongrequest; if (err) {
return next(err); err.status = codes.wrongrequest;
} return next(err);
else if (!item) { }
err = new Error("item not found"); else if (!item) {
err.status = codes.notfound; err = new Error("item not found");
return next(err); err.status = codes.notfound;
} return next(err);
// check that version is still accurate }
else if (user.__v !== item.__v) { // check that version is still accurate
err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again") else if (user.__v !== item.__v) {
err.status = codes.conflict; err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again")
return next(err); err.status = codes.conflict;
} return next(err);
// now update all fields in DB item with body data in variable video }
for (var field in UserModel.schema.paths) { // now update all fields in DB item with body data in variable video
if ((field !== '_id') && (field !== '__v')) { for (var field in UserModel.schema.paths) {
// this includes undefined. is important to reset attributes that are missing in req.body if ((field !== '_id') && (field !== '__v')) {
item.set(field, user[field]); // this includes undefined. is important to reset attributes that are missing in req.body
} item.set(field, user[field]);
} }
}
// update updatedAt and increase version // update updatedAt and increase version
item.updatedAt = new Date(); item.updatedAt = new Date();
item.increment(); // this sets __v++ item.increment(); // this sets __v++
item.save(function (err) { item.save(function (err) {
if (!err) { if (!err) {
res.locals.items = item; res.locals.items = item;
} else { } else {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
} }
UserModel.populate(item, {path: 'squadId'}, (err, extUser) => { UserModel.populate(item, {path: 'squadId'}, (err, extUser) => {
res.locals.items = extUser; res.locals.items = extUser;
res.locals.processed = true; res.locals.processed = true;
return next(); return next();
}) })
}); });
}) })
}) })
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => { .delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
UserModel.findByIdAndRemove(req.params.id, (err, item) => { UserModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
} }
else if (!item) { else if (!item) {
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// deleted all awardings linked to this user // deleted all awardings linked to this user
AwardingModel.find({userId: req.params.id}).remove().exec(); AwardingModel.find({userId: req.params.id}).remove().exec();
// check if signature exists and delete compressed and uncompressed file // check if signature exists and delete compressed and uncompressed file
const fileMinified = __dirname + '/../resource/signature/' + req.params.id + '.png'; const fileMinified = __dirname + '/../resource/signature/' + req.params.id + '.png';
if (fs.existsSync(fileMinified)) { if (fs.existsSync(fileMinified)) {
fs.unlink(fileMinified, (err) => { fs.unlink(fileMinified, (err) => {
}); });
} }
const file = __dirname + '/../resource/signature/big/' + req.params.id + '.png'; const file = __dirname + '/../resource/signature/big/' + req.params.id + '.png';
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
fs.unlink(file, (err) => { fs.unlink(file, (err) => {
}); });
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // 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; // user.use(..)
next(err); // this works because err is in normal case undefined and that is the same as no parameter res.locals.processed = true;
}); next(err); // this works because err is in normal case undefined and that is the same as no parameter
}) });
})
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -27,6 +27,7 @@ const CampaignModel = require('../models/campaign');
const WarModel = require('../models/war'); const WarModel = require('../models/war');
const PlayerModel = require('../models/player'); const PlayerModel = require('../models/player');
const LogKillModel = require('../models/logs/kill'); const LogKillModel = require('../models/logs/kill');
const LogVehicleKillModel = require('../models/logs/vehicle');
const LogRespawnModel = require('../models/logs/respawn'); const LogRespawnModel = require('../models/logs/respawn');
const LogReviveModel = require('../models/logs/revive'); const LogReviveModel = require('../models/logs/revive');
const LogTransportModel = require('../models/logs/transport'); const LogTransportModel = require('../models/logs/transport');
@ -38,87 +39,91 @@ const wars = express.Router();
// routes ********************** // routes **********************
wars.route('/') wars.route('/')
.get((req, res, next) => { .get((req, res, next) => {
let result = []; let result = [];
CampaignModel.find({}, {}, {sort: {timestamp: 'desc'}}, (err, campaigns) => { CampaignModel.find({}, {}, {sort: {timestamp: 'desc'}}, (err, campaigns) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
if (campaigns) {
WarModel.find({}, {}, {sort: {date: 'desc'}}, (err, wars) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
if (wars) {
campaigns.forEach(campaign => {
let entry = {_id: campaign._id, title: campaign.title, wars: []};
wars.forEach((war) => {
if (String(campaign._id) === String(war.campaign)) {
entry.wars.push(war);
}
});
result.push(entry);
});
res.locals.items = result;
}
res.locals.processed = true;
next();
}
)
;
}
})
})
.post(apiAuthenticationMiddleware, checkMT, upload.single('log'), (req, res, next) => {
const body = req.body;
const warBody = new WarModel(body);
if (req.file) {
fs.readFile(req.file.buffer, (file, err) => {
if (err) { if (err) {
err.status = codes.servererror;
return next(err); return next(err);
} }
const lineArray = file.toString().split("\n"); if (campaigns) {
const statsResult = parseWarLog(lineArray, warBody); WarModel.find({}, {}, {sort: {date: 'desc'}}, (err, wars) => {
statsResult.war.save((err, war) => { if (err) {
err.status = codes.servererror;
return next(err);
}
if (wars) {
campaigns.forEach(campaign => {
let entry = {_id: campaign._id, title: campaign.title, wars: []};
wars.forEach((war) => {
if (String(campaign._id) === String(war.campaign)) {
entry.wars.push(war);
}
});
result.push(entry);
});
res.locals.items = result;
}
res.locals.processed = true;
next();
}
)
;
}
})
})
.post(apiAuthenticationMiddleware, checkMT, upload.single('log'), (req, res, next) => {
const body = req.body;
const warBody = new WarModel(body);
if (req.file) {
fs.readFile(req.file.buffer, (file, err) => {
if (err) { if (err) {
return next(err); return next(err);
} }
PlayerModel.create(statsResult.players, function (err) { const lineArray = file.toString().split("\n");
const statsResult = parseWarLog(lineArray, warBody);
statsResult.war.save((err, war) => {
if (err) { if (err) {
return next(err); return next(err);
} }
LogKillModel.create(statsResult.kills, function () { PlayerModel.create(statsResult.players, function (err) {
LogRespawnModel.create(statsResult.respawn, function () { if (err) {
LogReviveModel.create(statsResult.revive, function () { return next(err);
LogFlagModel.create(statsResult.flag, function () { }
LogBudgetModel.create(statsResult.budget, function () { LogKillModel.create(statsResult.kills, function () {
LogTransportModel.create(statsResult.transport, function () { LogVehicleKillModel.create(statsResult.vehicles, function () {
LogPointsModel.create(statsResult.points, function () {
const folderName = __dirname + '/../resource/logs/' + war._id;
mkdirp(folderName, function (err) {
if (err) return next(err);
// save clean log file LogRespawnModel.create(statsResult.respawn, function () {
const cleanFile = fs.createWriteStream(folderName + '/clean.log'); LogReviveModel.create(statsResult.revive, function () {
statsResult.clean.forEach(cleanLine => { LogFlagModel.create(statsResult.flag, function () {
cleanFile.write(cleanLine + '\n\n') LogBudgetModel.create(statsResult.budget, function () {
}); LogTransportModel.create(statsResult.transport, function () {
cleanFile.end(); LogPointsModel.create(statsResult.points, function () {
const folderName = __dirname + '/../resource/logs/' + war._id;
mkdirp(folderName, function (err) {
if (err) return next(err);
// save raw log file // save clean log file
const rawFile = fs.createWriteStream(folderName + '/war.log'); const cleanFile = fs.createWriteStream(folderName + '/clean.log');
lineArray.forEach(rawLine => { statsResult.clean.forEach(cleanLine => {
rawFile.write(rawLine + '\n') cleanFile.write(cleanLine + '\n\n')
}); });
rawFile.end(); cleanFile.end();
res.status(codes.created); // save raw log file
res.locals.items = war; const rawFile = fs.createWriteStream(folderName + '/war.log');
next(); lineArray.forEach(rawLine => {
rawFile.write(rawLine + '\n')
});
rawFile.end();
res.status(codes.created);
res.locals.items = war;
next();
})
})
}) })
}) })
}) })
@ -128,95 +133,95 @@ wars.route('/')
}) })
}) })
}) })
}) });
});
} else { } else {
const err = new Error('no Logfile provided'); const err = new Error('no Logfile provided');
err.status = codes.wrongmediasend; err.status = codes.wrongmediasend;
next(err); next(err);
} }
}) })
.all( .all(
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
wars.route('/:id') wars.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
WarModel.findById(req.params.id, (err, item) => { WarModel.findById(req.params.id, (err, item) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
else if (!item) {
err = new Error("item not found");
err.status = codes.notfound;
return next(err);
}
PlayerModel.find({warId: item._id}, {}, {sort: {kill: 'desc'}}, (err, items) => {
if (err) { if (err) {
err.status = codes.servererror;
return next(err);
}
else if (!item) {
err = new Error("item not found");
err.status = codes.notfound;
return next(err);
}
PlayerModel.find({warId: item._id}, {}, {sort: {kill: 'desc'}}, (err, items) => {
if (err) {
return next(err);
}
const responseObj = item.toObject();
responseObj.players = items;
res.locals.items = responseObj;
return next();
});
});
})
.delete(apiAuthenticationMiddleware, checkMT, (req, res, next) => {
WarModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) {
err.status = codes.wrongrequest;
return next(err);
}
else if (!item) {
err = new Error("item not found");
err.status = codes.notfound;
return next(err); return next(err);
} }
const responseObj = item.toObject(); // delete linked appearances
responseObj.players = items; PlayerModel.find({warId: item._id}).remove().exec();
res.locals.items = responseObj; LogKillModel.find({war: item._id}).remove().exec();
return next(); LogRespawnModel.find({war: item._id}).remove().exec();
}); LogReviveModel.find({war: item._id}).remove().exec();
LogFlagModel.find({war: item._id}).remove().exec();
LogBudgetModel.find({war: item._id}).remove().exec();
LogTransportModel.find({war: item._id}).remove().exec();
LogPointsModel.find({war: item._id}).remove().exec();
}); // check if logfiles exist and delete from fs
}) const warDir = __dirname + '/../resource/logs/' + req.params.id;
.delete(apiAuthenticationMiddleware, checkMT, (req, res, next) => { if (fs.existsSync(warDir)) {
WarModel.findByIdAndRemove(req.params.id, (err, item) => { const cleanLog = warDir + '/clean.log';
if (err) { if (fs.existsSync(cleanLog)) {
err.status = codes.wrongrequest; fs.unlink(cleanLog, (err) => {
return next(err); });
} }
else if (!item) { const sourceLog = warDir + '/war.log';
err = new Error("item not found"); if (fs.existsSync(sourceLog)) {
err.status = codes.notfound; fs.unlink(sourceLog, (err) => {
return next(err); });
} }
fs.rmdir(warDir, (err) => {
// delete linked appearances
PlayerModel.find({warId: item._id}).remove().exec();
LogKillModel.find({war: item._id}).remove().exec();
LogRespawnModel.find({war: item._id}).remove().exec();
LogReviveModel.find({war: item._id}).remove().exec();
LogFlagModel.find({war: item._id}).remove().exec();
LogBudgetModel.find({war: item._id}).remove().exec();
LogTransportModel.find({war: item._id}).remove().exec();
LogPointsModel.find({war: item._id}).remove().exec();
// check if logfiles exist and delete from fs
const warDir = __dirname + '/../resource/logs/' + req.params.id;
if (fs.existsSync(warDir)) {
const cleanLog = warDir + '/clean.log';
if (fs.existsSync(cleanLog)) {
fs.unlink(cleanLog, (err) => {
}); });
} }
const sourceLog = warDir + '/war.log'; // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
if (fs.existsSync(sourceLog)) { // user.use(..)
fs.unlink(sourceLog, (err) => { res.locals.processed = true;
}); next();
}
fs.rmdir(warDir, (err) => {
});
}
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..)
res.locals.processed = true;
next();
})
}) })
})
.all( .all(
routerHandling.httpMethodNotAllowed 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

View File

@ -24,13 +24,13 @@ describe('Awardings', () => {
describe('/GET awardings', () => { describe('/GET awardings', () => {
it('it should GET all awardings', (done) => { it('it should GET all awardings', (done) => {
chai.request(server) chai.request(server)
.get(urls.awards) .get(urls.awards)
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.success); res.should.have.status(codes.success);
res.body.should.be.a('array'); res.body.should.be.a('array');
res.body.length.should.be.eql(0); res.body.length.should.be.eql(0);
done(); done();
}); });
}); });
}); });
@ -41,15 +41,15 @@ describe('Awardings', () => {
it('it should not POST an awarding without auth-token provided', (done) => { it('it should not POST an awarding without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.post(urls.awards) .post(urls.awards)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -60,15 +60,15 @@ describe('Awardings', () => {
it('it should not PATCH an awarding without auth-token provided', (done) => { it('it should not PATCH an awarding without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.patch(urls.awards + '/someId') .patch(urls.awards + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -81,28 +81,28 @@ describe('Awardings', () => {
it('it should not accept DELETE method without id in url', (done) => { it('it should not accept DELETE method without id in url', (done) => {
chai.request(server) chai.request(server)
.delete(urls.awards) .delete(urls.awards)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.wrongmethod); res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('error').property('message') res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.awards); .eql('this method is not allowed at ' + urls.awards);
done(); done();
}); });
}); });
it('it should not DELETE an awarding without auth-token provided', (done) => { it('it should not DELETE an awarding without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.delete(urls.awards + '/someId') .delete(urls.awards + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });

View File

@ -24,15 +24,15 @@ describe('Command', () => {
describe('/POST command to create signature', () => { describe('/POST command to create signature', () => {
it('it should not succeed without auth-token provided', (done) => { it('it should not succeed without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.post(urls.cmdCreateSig + "/someId") .post(urls.cmdCreateSig + "/someId")
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });

View File

@ -24,13 +24,13 @@ describe('Decorations', () => {
describe('/GET decorations', () => { describe('/GET decorations', () => {
it('it should GET all the decorations', (done) => { it('it should GET all the decorations', (done) => {
chai.request(server) chai.request(server)
.get(urls.decorations) .get(urls.decorations)
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.success); res.should.have.status(codes.success);
res.body.should.be.a('array'); res.body.should.be.a('array');
res.body.length.should.be.eql(0); res.body.length.should.be.eql(0);
done(); done();
}); });
}); });
}); });
@ -41,15 +41,15 @@ describe('Decorations', () => {
it('it should not POST a decoration without auth-token provided', (done) => { it('it should not POST a decoration without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.post(urls.decorations) .post(urls.decorations)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -60,15 +60,15 @@ describe('Decorations', () => {
it('it should not PATCH a decoration without auth-token provided', (done) => { it('it should not PATCH a decoration without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.patch(urls.decorations + '/someId') .patch(urls.decorations + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -80,28 +80,28 @@ describe('Decorations', () => {
describe('/DELETE decorations', () => { describe('/DELETE decorations', () => {
it('it should not accept DELETE method without id in url', (done) => { it('it should not accept DELETE method without id in url', (done) => {
chai.request(server) chai.request(server)
.delete(urls.decorations) .delete(urls.decorations)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.wrongmethod); res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('error').property('message') res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.decorations); .eql('this method is not allowed at ' + urls.decorations);
done(); done();
}); });
}); });
it('it should not DELETE a decoration without auth-token provided', (done) => { it('it should not DELETE a decoration without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.delete(urls.decorations + '/someId') .delete(urls.decorations + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });

View File

@ -24,13 +24,13 @@ describe('Ranks', () => {
describe('/GET ranks', () => { describe('/GET ranks', () => {
it('it should GET all the ranks', (done) => { it('it should GET all the ranks', (done) => {
chai.request(server) chai.request(server)
.get(urls.ranks) .get(urls.ranks)
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.success); res.should.have.status(codes.success);
res.body.should.be.a('array'); res.body.should.be.a('array');
res.body.length.should.be.eql(0); res.body.length.should.be.eql(0);
done(); done();
}); });
}); });
}); });
@ -41,15 +41,15 @@ describe('Ranks', () => {
it('it should not POST a rank without auth-token provided', (done) => { it('it should not POST a rank without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.post(urls.ranks) .post(urls.ranks)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}) })
@ -60,15 +60,15 @@ describe('Ranks', () => {
it('it should not PATCH a rank without auth-token provided', (done) => { it('it should not PATCH a rank without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.patch(urls.ranks + '/someId') .patch(urls.ranks + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -80,29 +80,29 @@ describe('Ranks', () => {
describe('/DELETE ranks', () => { describe('/DELETE ranks', () => {
it('it should not accept DELETE method without id in url', (done) => { it('it should not accept DELETE method without id in url', (done) => {
chai.request(server) chai.request(server)
.delete(urls.ranks) .delete(urls.ranks)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.wrongmethod); res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('error').property('message') res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.ranks); .eql('this method is not allowed at ' + urls.ranks);
done(); done();
}); });
}); });
it('it should not DELETE a rank without auth-token provided', (done) => { it('it should not DELETE a rank without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.delete(urls.ranks + '/someId') .delete(urls.ranks + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });

View File

@ -24,13 +24,13 @@ describe('Squads', () => {
describe('/GET squads', () => { describe('/GET squads', () => {
it('it should GET all the squads', (done) => { it('it should GET all the squads', (done) => {
chai.request(server) chai.request(server)
.get(urls.squads) .get(urls.squads)
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.success); res.should.have.status(codes.success);
res.body.should.be.a('array'); res.body.should.be.a('array');
res.body.length.should.be.eql(0); res.body.length.should.be.eql(0);
done(); done();
}); });
}); });
}); });
@ -41,15 +41,15 @@ describe('Squads', () => {
it('it should not POST a squad without auth-token provided', (done) => { it('it should not POST a squad without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.post(urls.squads) .post(urls.squads)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -60,15 +60,15 @@ describe('Squads', () => {
it('it should not PATCH a squad without auth-token provided', (done) => { it('it should not PATCH a squad without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.patch(urls.squads + '/someId') .patch(urls.squads + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -80,28 +80,28 @@ describe('Squads', () => {
describe('/DELETE squads', () => { describe('/DELETE squads', () => {
it('it should not accept DELETE method without id in url', (done) => { it('it should not accept DELETE method without id in url', (done) => {
chai.request(server) chai.request(server)
.delete(urls.squads) .delete(urls.squads)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.wrongmethod); res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('error').property('message') res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.squads); .eql('this method is not allowed at ' + urls.squads);
done(); done();
}); });
}); });
it('it should not DELETE a squad without auth-token provided', (done) => { it('it should not DELETE a squad without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.delete(urls.squads + '/someId') .delete(urls.squads + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });

View File

@ -25,13 +25,13 @@ describe('Users', () => {
describe('/GET users', () => { describe('/GET users', () => {
it('it should GET all the users', (done) => { it('it should GET all the users', (done) => {
chai.request(server) chai.request(server)
.get(urls.users) .get(urls.users)
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.success); res.should.have.status(codes.success);
res.body.should.be.a('array'); res.body.should.be.a('array');
res.body.length.should.be.eql(0); res.body.length.should.be.eql(0);
done(); done();
}); });
}); });
}); });
@ -73,15 +73,15 @@ describe('Users', () => {
it('it should not POST a user without auth-token provided', (done) => { it('it should not POST a user without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.post(urls.users) .post(urls.users)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
// it('it should POST a user with provided username', (done) => { // it('it should POST a user with provided username', (done) => {
@ -109,15 +109,15 @@ describe('Users', () => {
it('it should not PATCH a user without auth-token provided', (done) => { it('it should not PATCH a user without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.patch(urls.users + '/someId') .patch(urls.users + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -129,28 +129,28 @@ describe('Users', () => {
describe('/DELETE users', () => { describe('/DELETE users', () => {
it('it should not accept DELETE method without id in url', (done) => { it('it should not accept DELETE method without id in url', (done) => {
chai.request(server) chai.request(server)
.delete(urls.users) .delete(urls.users)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.wrongmethod); res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('error').property('message') res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.users); .eql('this method is not allowed at ' + urls.users);
done(); done();
}); });
}); });
it('it should not DELETE a user without auth-token provided', (done) => { it('it should not DELETE a user without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.delete(urls.users + '/someId') .delete(urls.users + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });

View File

@ -20,13 +20,13 @@ describe('Wars', () => {
describe('/GET wars', () => { describe('/GET wars', () => {
it('it should GET all wars', (done) => { it('it should GET all wars', (done) => {
chai.request(server) chai.request(server)
.get(urls.wars) .get(urls.wars)
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.success); res.should.have.status(codes.success);
res.body.should.be.a('array'); res.body.should.be.a('array');
res.body.length.should.be.eql(0); res.body.length.should.be.eql(0);
done(); done();
}); });
}); });
}); });
@ -37,15 +37,15 @@ describe('Wars', () => {
it('it should not POST a war without auth-token provided', (done) => { it('it should not POST a war without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.post(urls.wars) .post(urls.wars)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });
@ -56,28 +56,28 @@ describe('Wars', () => {
it('it should not accept DELETE method without id in url', (done) => { it('it should not accept DELETE method without id in url', (done) => {
chai.request(server) chai.request(server)
.delete(urls.wars) .delete(urls.wars)
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.wrongmethod); res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('error').property('message') res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.wars); .eql('this method is not allowed at ' + urls.wars);
done(); done();
}); });
}); });
it('it should not DELETE an awarding without auth-token provided', (done) => { it('it should not DELETE an awarding without auth-token provided', (done) => {
chai.request(server) chai.request(server)
.delete(urls.wars + '/someId') .delete(urls.wars + '/someId')
.send({}) .send({})
.end((err, res) => { .end((err, res) => {
res.should.have.status(codes.forbidden); res.should.have.status(codes.forbidden);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('success').eql(false); res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.'); res.body.should.have.property('message').eql('No token provided.');
done(); done();
}); });
}); });
}); });

View File

@ -5,6 +5,7 @@ const playerArrayContains = require('./util').playerArrayContains;
const WHITESPACE = ' '; const WHITESPACE = ' ';
const parseWarLog = (lineArray, war) => { const parseWarLog = (lineArray, war) => {
const NAME_TOO_LONG_ERROR = 'Error: ENAMETOOLONG: name too long, open \''; const NAME_TOO_LONG_ERROR = 'Error: ENAMETOOLONG: name too long, open \'';
const stats = { const stats = {
@ -13,6 +14,7 @@ const parseWarLog = (lineArray, war) => {
budget: [], budget: [],
points: [], points: [],
kills: [], kills: [],
vehicles: [],
respawn: [], respawn: [],
revive: [], revive: [],
flag: [], flag: [],
@ -20,10 +22,21 @@ const parseWarLog = (lineArray, war) => {
players: [] players: []
}; };
const addPlayerIfNotExists = (inputPlayer) => { const vehicleBlacklist = [
'Prowler (Unbewaffnet)', 'Prowler (Bewaffnet)', 'Hunter',
'HEMTT Transporter', 'HEMTT Transporter (abgedeckt)', 'HEMTT SanitÀtsfahrzeug',
'Remote Designator [NATO]', 'UGV Stomper',
'Qilin (Unbewaffnet)', 'Qilin (Bewaffnet)', 'Ifrit',
'Tempest-Transporter', 'Tempest-Transporter (abgedeckt)', 'Tempest SanitÀtsfahrzeug',
'Remote Designator [CSAT]', 'UBF Saif',
'Quad Bike', 'HuntIR'
];
const addPlayerIfNotExists = (inputPlayer, steamUUID) => {
const player = getPlayerAndFractionFromString(inputPlayer); const player = getPlayerAndFractionFromString(inputPlayer);
if (player && player.name && player.fraction && !playerArrayContains(stats.players, player)) { if (player && player.name && player.fraction && !playerArrayContains(stats.players, player)) {
player['warId'] = war._id; player['warId'] = war._id;
player['steamUUID'] = steamUUID;
stats.players.push(player); stats.players.push(player);
} }
}; };
@ -37,23 +50,38 @@ const parseWarLog = (lineArray, war) => {
} }
/** /**
* KILLS * KILLS & VEHICLE KILLS
*/ */
if (line.includes('(Abschuss)') && !line.includes('Fahrzeug')) { if (line.includes('(Abschuss)')) {
stats.clean.push(line); stats.clean.push(line);
const shooterString = line.substring(line.lastIndexOf(' von: ') + 6, line.lastIndexOf('."')); const shooterString = line.substring(line.lastIndexOf(' von: ') + 6, line.lastIndexOf('."'));
const shooter = getPlayerAndFractionFromString(shooterString); const shooter = getPlayerAndFractionFromString(shooterString);
const targetString = line.substring(line.lastIndexOf(' --- ') + 5, line.lastIndexOf(' von:'));
const target = getPlayerAndFractionFromString(targetString);
stats.kills.push({ if (line.includes('Fahrzeug:')) {
war: war._id, const targetString = line.substring(line.lastIndexOf(' --- Fahrzeug: ') + 15, line.lastIndexOf(' von:'));
time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]), const target = getVehicleAndFractionFromString(targetString);
shooter: shooter ? shooter.name : null, if (target && shooter && target.fraction !== shooter.fraction) {
target: target ? target.name : null, stats.vehicles.push({
friendlyFire: shooter ? target.fraction === shooter.fraction : false, war: war._id,
fraction: shooter ? shooter.fraction : 'NONE' time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]),
}); shooter: shooter ? shooter.name : null,
target: target ? target.name : null,
fraction: shooter ? shooter.fraction : 'NONE'
})
}
} else {
const targetString = line.substring(line.lastIndexOf(' --- ') + 5, line.lastIndexOf(' von:'));
const target = getPlayerAndFractionFromString(targetString);
stats.kills.push({
war: war._id,
time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]),
shooter: shooter ? shooter.name : null,
target: target ? target.name : null,
friendlyFire: shooter ? target.fraction === shooter.fraction : false,
fraction: shooter ? shooter.fraction : 'NONE'
});
}
} }
/** /**
@ -171,7 +199,8 @@ const parseWarLog = (lineArray, war) => {
*/ */
else if (line.includes('(Fraktionsuebersicht)')) { else if (line.includes('(Fraktionsuebersicht)')) {
const playerString = line.substring(line.lastIndexOf('--- ') + 4, line.lastIndexOf(', PUID')); const playerString = line.substring(line.lastIndexOf('--- ') + 4, line.lastIndexOf(', PUID'));
addPlayerIfNotExists(playerString) const playerUUID = line.substring(line.lastIndexOf('PUID ') + 5, line.lastIndexOf('"'));
addPlayerIfNotExists(playerString, playerUUID)
} }
}); });
@ -179,6 +208,7 @@ const parseWarLog = (lineArray, war) => {
const playerName = stats.players[i].name; const playerName = stats.players[i].name;
stats.players[i]['respawn'] = stats.respawn.filter(res => res.player === playerName).length; stats.players[i]['respawn'] = stats.respawn.filter(res => res.player === playerName).length;
stats.players[i]['kill'] = stats.kills.filter(kill => kill.shooter === playerName && !kill.friendlyFire).length; stats.players[i]['kill'] = stats.kills.filter(kill => kill.shooter === playerName && !kill.friendlyFire).length;
stats.players[i]['vehicle'] = stats.vehicles.filter(vehicle => vehicle.shooter === playerName && vehicleBlacklist.indexOf(vehicle.target) < 0).length;
stats.players[i]['friendlyFire'] = stats.kills.filter(kill => kill.shooter === playerName && kill.friendlyFire).length; stats.players[i]['friendlyFire'] = stats.kills.filter(kill => kill.shooter === playerName && kill.friendlyFire).length;
stats.players[i]['death'] = stats.kills.filter(kill => kill.target === playerName).length; stats.players[i]['death'] = stats.kills.filter(kill => kill.target === playerName).length;
stats.players[i]['revive'] = stats.revive.filter(rev => rev.medic === playerName && !rev.stabilized).length; stats.players[i]['revive'] = stats.revive.filter(rev => rev.medic === playerName && !rev.stabilized).length;
@ -231,6 +261,21 @@ const getPlayerAndFractionFromString = (nameAndFractionString) => {
} }
}; };
const getVehicleAndFractionFromString = (nameAndFractionString) => {
const nameArray = nameAndFractionString.trim().split(WHITESPACE);
const fractionPart = nameArray[nameArray.length - 1];
// escape it is some parachute fraction identifier
if (fractionPart === 'OPF_F' || fractionPart === 'BLU_F') {
return;
}
const fraction = fractionPart === '(OPT_NATO)' || fractionPart === '(OPT_NATO_T)' ? 'BLUFOR' : 'OPFOR';
const name = nameAndFractionString.substring(0, nameAndFractionString.indexOf(fractionPart) - 1);
return {name: name, fraction: fraction};
};
const transformMoneyString = (budgetString) => { const transformMoneyString = (budgetString) => {
if (!budgetString.includes('e+')) { if (!budgetString.includes('e+')) {
return parseInt(budgetString); return parseInt(budgetString);

View File

@ -31,79 +31,79 @@ let createSignature = (userId, res, next) => {
return next(error); return next(error);
} }
UserModel.findById(userId, ['username', 'rankLvl', 'squadId']) UserModel.findById(userId, ['username', 'rankLvl', 'squadId'])
.populate('squadId', ['name', 'fraction']) .populate('squadId', ['name', 'fraction'])
.exec() .exec()
.then((resUser) => { .then((resUser) => {
user = resUser; user = resUser;
let platePath; let platePath;
if (resUser && resUser.squadId && resUser.squadId.fraction === 'BLUFOR') { if (resUser && resUser.squadId && resUser.squadId.fraction === 'BLUFOR') {
platePath = __dirname + '/backplate/blufor' + fileExt; platePath = __dirname + '/backplate/blufor' + fileExt;
} else if (resUser && resUser.squadId && resUser.squadId.fraction === 'OPFOR') { } else if (resUser && resUser.squadId && resUser.squadId.fraction === 'OPFOR') {
platePath = __dirname + '/backplate/opfor' + fileExt; platePath = __dirname + '/backplate/opfor' + fileExt;
} }
// kill process on undefined platePath // kill process on undefined platePath
if (!platePath) { if (!platePath) {
throw new Error('Fraction not defined for user with id ' + userId); throw new Error('Fraction not defined for user with id ' + userId);
} }
return jimp.read(platePath) return jimp.read(platePath)
}) })
.then((image) => { .then((image) => {
loadedImage = image; loadedImage = image;
}).then(() => { }).then(() => {
return jimp.loadFont(__dirname + '/font/DEVAJU_SANS_19.fnt'); return jimp.loadFont(__dirname + '/font/DEVAJU_SANS_19.fnt');
}) })
.then((font) => { .then((font) => {
loadedImage.print(font, 128, 8, user.username) loadedImage.print(font, 128, 8, user.username)
}) })
.then(() => { .then(() => {
return jimp.loadFont(__dirname + '/font/DEJAVU_SANS_13.fnt'); return jimp.loadFont(__dirname + '/font/DEJAVU_SANS_13.fnt');
}) })
.then((font) => { .then((font) => {
loadedImage.print(font, 128, 35, user.squadId.name); loadedImage.print(font, 128, 35, user.squadId.name);
return font; return font;
}) })
.then((font) => { .then((font) => {
let rankH, rankW, rankX, rankY; let rankH, rankW, rankX, rankY;
RankModel.findOne({'level': user.rankLvl, 'fraction': user.squadId.fraction}, (err, result) => { RankModel.findOne({'level': user.rankLvl, 'fraction': user.squadId.fraction}, (err, result) => {
if (err) { if (err) {
return next(err) return next(err)
} }
if (result) { if (result) {
if (user.squadId.fraction === 'BLUFOR') { if (user.squadId.fraction === 'BLUFOR') {
rankW = 25; rankW = 25;
rankH = 60; rankH = 60;
rankX = 36; rankX = 36;
rankY = 34; rankY = 34;
} else { } else {
rankW = 37; rankW = 37;
rankH = 58; rankH = 58;
rankX = 30; rankX = 30;
rankY = 34; rankY = 34;
} }
jimp.read(resourceDir + 'rank/' + result._id + fileExt) jimp.read(resourceDir + 'rank/' + result._id + fileExt)
.then((rankImage) => { .then((rankImage) => {
rankImage.resize(rankW, rankH); rankImage.resize(rankW, rankH);
loadedImage loadedImage
.print(font, 128, 55, result.name) .print(font, 128, 55, result.name)
.composite(rankImage, rankX, rankY) .composite(rankImage, rankX, rankY)
}) })
.then(() => { .then(() => {
addDecorationsAndSave(userId, loadedImage, res, next); addDecorationsAndSave(userId, loadedImage, res, next);
}) })
} else { } else {
// user has not any assignable rank in his fraction at this point, // 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 // e.g. assigned rank has been deleted or switched fraction so rankLvl is not defined
addDecorationsAndSave(userId, loadedImage, res, next); addDecorationsAndSave(userId, loadedImage, res, next);
} }
}) })
}) })
.catch((err) => { .catch((err) => {
next(err); next(err);
}) })
}; };
@ -130,110 +130,110 @@ let addDecorationsAndSave = (userId, loadedImage, res, next) => {
'userId': userId, 'userId': userId,
'confirmed': 1 'confirmed': 1
}, ['decorationId', 'date']).populate('decorationId', ['isMedal', 'fraction']) }, ['decorationId', 'date']).populate('decorationId', ['isMedal', 'fraction'])
.exec((err, awardings) => { .exec((err, awardings) => {
if (err) { if (err) {
return next(err); return next(err);
} }
if (awardings.length > 0) { if (awardings.length > 0) {
//TODO: simplify this sorting hell //TODO: simplify this sorting hell
awardings.sort((a1, a2) => { awardings.sort((a1, a2) => {
if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) { if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) {
if (a1.decorationId.fraction === a2.decorationId.fraction) { if (a1.decorationId.fraction === a2.decorationId.fraction) {
if (a1.date !== a2.date) { if (a1.date !== a2.date) {
if (a1.date < a2.date) { if (a1.date < a2.date) {
return -1; return -1;
} }
if (a1.date > a2.date) { if (a1.date > a2.date) {
return 1; return 1;
} }
} }
return 0; return 0;
} else { } else {
if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') { if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') {
return -1; return -1;
} }
if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') { if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') {
return 1; return 1;
} }
} }
} }
if (a1.decorationId.isMedal !== a2.decorationId.isMedal) { if (a1.decorationId.isMedal !== a2.decorationId.isMedal) {
if (a1.decorationId.isMedal && !a2.decorationId.isMedal) { if (a1.decorationId.isMedal && !a2.decorationId.isMedal) {
return 1; return 1;
} }
if (!a1.decorationId.isMedal && a2.decorationId.isMedal) { if (!a1.decorationId.isMedal && a2.decorationId.isMedal) {
return -1; return -1;
} }
} }
if (a1.decorationId.isMedal && a2.decorationId.isMedal) { if (a1.decorationId.isMedal && a2.decorationId.isMedal) {
if (a1.date !== a2.date) { if (a1.date !== a2.date) {
if (a1.date < a2.date) { if (a1.date < a2.date) {
return -1; return -1;
} }
if (a1.date > a2.date) { if (a1.date > a2.date) {
return 1; return 1;
} }
} }
return 0; return 0;
} }
}); });
// use synchronized call to keep correct order of decorations // use synchronized call to keep correct order of decorations
async.eachSeries(awardings, (award, callback) => { async.eachSeries(awardings, (award, callback) => {
jimp.read(resourceDir + 'decoration/' + award.decorationId._id + fileExt).then((decorationImage) => { jimp.read(resourceDir + 'decoration/' + award.decorationId._id + fileExt).then((decorationImage) => {
if (award.decorationId.isMedal) { if (award.decorationId.isMedal) {
decorationImage.resize(medalW, medalH); decorationImage.resize(medalW, medalH);
loadedImage.composite(decorationImage, medalPx, medalPy); loadedImage.composite(decorationImage, medalPx, medalPy);
if (medalPy === 5) { if (medalPy === 5) {
medalPx = medalPx - 1 - medalW; medalPx = medalPx - 1 - medalW;
} else { } else {
medalPx = medalPx + 1 + medalW; medalPx = medalPx + 1 + medalW;
} }
if (medalPx <= 300) { if (medalPx <= 300) {
medalPy = medalPy + 3 + medalH; medalPy = medalPy + 3 + medalH;
} }
} else { } else {
decorationImage.resize(ribbonW, ribbonH); decorationImage.resize(ribbonW, ribbonH);
loadedImage.composite(decorationImage, ribbonPx, ribbonPy); loadedImage.composite(decorationImage, ribbonPx, ribbonPy);
ribbonPx = ribbonPx - 2 - ribbonW; ribbonPx = ribbonPx - 2 - ribbonW;
if (ribbonPx <= 154) { if (ribbonPx <= 154) {
ribbonPy = ribbonPy - 3 - ribbonH; ribbonPy = ribbonPy - 3 - ribbonH;
ribbonPx = 598; ribbonPx = 598;
} }
} }
callback(); callback();
}) })
}, (err) => { }, (err) => {
if (err) { if (err) {
throw err; throw err;
} }
compareImagesAndSave(loadedImage, userId, res, next); compareImagesAndSave(loadedImage, userId, res, next);
}); });
} else { } else {
compareImagesAndSave(loadedImage, userId, res, next); compareImagesAndSave(loadedImage, userId, res, next);
} }
} }
) )
}; };
let compareImagesAndSave = (generatedImage, userId, res, next) => { let compareImagesAndSave = (generatedImage, userId, res, next) => {
return jimp.read(resourceDir + 'signature/big/' + userId + fileExt) return jimp.read(resourceDir + 'signature/big/' + userId + fileExt)
.then((oldImage) => { .then((oldImage) => {
// compare hashes of image map to recognize difference // compare hashes of image map to recognize difference
const sig1 = SHA1(generatedImage.bitmap.data); const sig1 = SHA1(generatedImage.bitmap.data);
const sig2 = SHA1(oldImage.bitmap.data); const sig2 = SHA1(oldImage.bitmap.data);
if (sig1 !== sig2) { if (sig1 !== sig2) {
saveJimpImageAndCompress(generatedImage, userId, res, next) saveJimpImageAndCompress(generatedImage, userId, res, next)
} else { } else {
res.locals.items = {status: 'nothing to do'}; res.locals.items = {status: 'nothing to do'};
next(); next();
} }
}) })
.catch((err) => { .catch((err) => {
saveJimpImageAndCompress(generatedImage, userId, res, next); saveJimpImageAndCompress(generatedImage, userId, res, next);
}) })
}; };

View File

@ -1,5 +1,10 @@
"use strict"; "use strict";
const isSteamUUID = (input) => {
const steamUIDPattern = new RegExp("[0-9]{17}");
return steamUIDPattern.test(input)
};
const sortCollectionBy = (collection, key) => { const sortCollectionBy = (collection, key) => {
collection.sort((a, b) => { collection.sort((a, b) => {
a = a[key].toLowerCase(); a = a[key].toLowerCase();
@ -44,6 +49,7 @@ const decimalToTimeString = (decimal) => {
(seconds < 10 ? '0' + seconds : seconds); (seconds < 10 ? '0' + seconds : seconds);
}; };
exports.isSteamUUID = isSteamUUID;
exports.sortCollection = sortCollectionBy; exports.sortCollection = sortCollectionBy;
exports.playerArrayContains = playerArrayContains; exports.playerArrayContains = playerArrayContains;
exports.timeStringToDecimal = timeStringToDecimal; exports.timeStringToDecimal = timeStringToDecimal;

View File

@ -1,6 +1,6 @@
{ {
"name": "opt-cc", "name": "opt-cc",
"version": "1.6.12", "version": "1.7.0",
"author": "Florian Hartwich <hardi@noarch.de>", "author": "Florian Hartwich <hardi@noarch.de>",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@ -1,5 +1,5 @@
.overview { .overview {
padding: 80px 0 0 10%!important; padding: 80px 0 0 10% !important;
} }
.trash { .trash {

View File

@ -45,19 +45,19 @@ export class AdminComponent {
} }
this.appUserService.updateUser(updateObject) this.appUserService.updateUser(updateObject)
.subscribe(user => { .subscribe(user => {
this.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
} }
deleteUser(user) { deleteUser(user) {
if (confirm('Soll der Nutzer "' + user.username + '" wirklich gelöscht werden?')) { if (confirm('Soll der Nutzer "' + user.username + '" wirklich gelöscht werden?')) {
this.appUserService.deleteUser(user) this.appUserService.deleteUser(user)
.subscribe((res) => { .subscribe((res) => {
}) })
} }
} }

View File

@ -30,7 +30,7 @@ li {
} }
.version-label { .version-label {
display:block; display: block;
position: fixed; position: fixed;
top: 32px; top: 32px;
left: 106px; left: 106px;

View File

@ -8,7 +8,7 @@
.army-member-view-container { .army-member-view-container {
width: 90%; width: 90%;
min-width: 870px; min-width: 870px;
margin:auto margin: auto
} }
.return-button { .return-button {

View File

@ -2,7 +2,8 @@
<div class="army-member-view-container"> <div class="army-member-view-container">
<div class="return-button"> <div class="return-button">
<span class="btn btn-default" style="position:absolute;" (click)="backToOverview()">< zurück zur Übersicht</span> <span class="btn btn-default" style="position:absolute;" (click)="backToOverview()">< zurück zur Übersicht</span>
<h3 class="text-center" style="font-weight: 600" [style.color]="user.squadId?.fraction === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR"> <h3 class="text-center" style="font-weight: 600"
[style.color]="user.squadId?.fraction === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
Auszeichnungen von {{user.username}} Auszeichnungen von {{user.username}}
</h3> </h3>
</div> </div>

View File

@ -41,16 +41,16 @@ export class ArmyMemberComponent {
this.document.getElementById('right').setAttribute('style', CSSHelpers.getBackgroundCSS('../assets/bg.jpg')); this.document.getElementById('right').setAttribute('style', CSSHelpers.getBackgroundCSS('../assets/bg.jpg'));
this.subscription = this.route.params this.subscription = this.route.params
.map(params => params['id']) .map(params => params['id'])
.filter(id => id != undefined) .filter(id => id != undefined)
.flatMap(id => this.userService.getUser(id)) .flatMap(id => this.userService.getUser(id))
.subscribe(user => { .subscribe(user => {
this.user = user; this.user = user;
this.signatureUrl = window.location.origin + '/resource/signature/' + user._id + '.png'; this.signatureUrl = window.location.origin + '/resource/signature/' + user._id + '.png';
this.awardingService.getUserAwardings(user._id).subscribe((awards => { this.awardingService.getUserAwardings(user._id).subscribe((awards => {
this.awards = awards; this.awards = awards;
})); }));
}); });
}; };

View File

@ -31,9 +31,9 @@ export class ArmyComponent {
// init army data // init army data
this.armyService.getArmy() this.armyService.getArmy()
.subscribe(army => { .subscribe(army => {
this.army = army; this.army = army;
}); });
}; };
ngOnDestroy() { ngOnDestroy() {

View File

@ -23,10 +23,10 @@
<div> <div>
<decoration-item *ngFor="let decoration of decorations$ | async" <decoration-item *ngFor="let decoration of decorations$ | async"
[decoration]="decoration" [decoration]="decoration"
(decorationDelete)="deleteDecoration(decoration)" (decorationDelete)="deleteDecoration(decoration)"
(decorationSelected)="selectDecoration($event)" (decorationSelected)="selectDecoration($event)"
[selected]="decoration._id == selectedDecorationId"> [selected]="decoration._id == selectedDecorationId">
</decoration-item> </decoration-item>
</div> </div>

View File

@ -35,17 +35,17 @@ export class DecorationListComponent implements OnInit {
this.decorations$ = this.decorationService.decorations$; this.decorations$ = this.decorationService.decorations$;
const paramsStream = this.route.queryParams const paramsStream = this.route.queryParams
.map(params => decodeURI(params['query'] || '')) .map(params => decodeURI(params['query'] || ''))
.do(query => this.searchTerm.setValue(query)); .do(query => this.searchTerm.setValue(query));
const searchTermStream = this.searchTerm.valueChanges const searchTermStream = this.searchTerm.valueChanges
.debounceTime(400) .debounceTime(400)
.do(query => this.adjustBrowserUrl(query)); .do(query => this.adjustBrowserUrl(query));
Observable.merge(paramsStream, searchTermStream) Observable.merge(paramsStream, searchTermStream)
.distinctUntilChanged() .distinctUntilChanged()
.switchMap(query => this.decorationService.findDecorations(query, this.radioModel)) .switchMap(query => this.decorationService.findDecorations(query, this.radioModel))
.subscribe(); .subscribe();
} }
openNewDecorationForm() { openNewDecorationForm() {
@ -65,8 +65,8 @@ export class DecorationListComponent implements OnInit {
if (confirm('Soll die Auszeichnung "' + decoration.name + '" (' + fraction + ') wirklich gelöscht werden?')) { if (confirm('Soll die Auszeichnung "' + decoration.name + '" (' + fraction + ') wirklich gelöscht werden?')) {
this.decorationService.deleteDecoration(decoration) this.decorationService.deleteDecoration(decoration)
.subscribe((res) => { .subscribe((res) => {
}) })
} }
} }

View File

@ -35,13 +35,13 @@ export class EditDecorationComponent {
ngOnInit() { ngOnInit() {
this.subscription = this.route.params this.subscription = this.route.params
.map(params => params['id']) .map(params => params['id'])
.filter(id => id != undefined) .filter(id => id != undefined)
.flatMap(id => this.decorationService.getDecoration(id)) .flatMap(id => this.decorationService.getDecoration(id))
.subscribe(decoration => { .subscribe(decoration => {
this.decoration = decoration; this.decoration = decoration;
this.imagePreviewSrc = 'resource/decoration/' + this.decoration._id + '.png?' + Date.now(); this.imagePreviewSrc = 'resource/decoration/' + this.decoration._id + '.png?' + Date.now();
}); });
} }
ngOnDestroy() { ngOnDestroy() {
@ -64,9 +64,9 @@ export class EditDecorationComponent {
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(rank => { .subscribe(rank => {
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
}) })
} else { } else {
return window.alert(`Bild ist ein Pflichtfeld`); return window.alert(`Bild ist ein Pflichtfeld`);
} }
@ -76,16 +76,16 @@ export class EditDecorationComponent {
} }
delete this.decoration['__v']; delete this.decoration['__v'];
this.decorationService.submitDecoration(this.decoration, file) this.decorationService.submitDecoration(this.decoration, file)
.subscribe(rank => { .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.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
} }
} }

View File

@ -35,18 +35,18 @@ export class LoginComponent implements OnInit {
if (username.length > 0 && password.length > 0) { if (username.length > 0 && password.length > 0) {
this.loading = true; this.loading = true;
this.loginService.login(username, password) this.loginService.login(username, password)
.subscribe( .subscribe(
data => { data => {
this.router.navigate([this.returnUrl]); this.router.navigate([this.returnUrl]);
}, },
error => { error => {
this.error = error._body; this.error = error._body;
this.showErrorLabel = true; this.showErrorLabel = true;
setTimeout(() => { setTimeout(() => {
this.showErrorLabel = false; this.showErrorLabel = false;
}, 4000); }, 4000);
this.loading = false; this.loading = false;
}); });
} }
} }
} }

View File

@ -38,19 +38,19 @@ export class SignupComponent implements OnInit {
if (username.length > 0 && password.length > 0 && secret.length > 0) { if (username.length > 0 && password.length > 0 && secret.length > 0) {
this.loading = true; this.loading = true;
this.loginService.signUp(username, password, secret) this.loginService.signUp(username, password, secret)
.subscribe( .subscribe(
data => { data => {
this.loading = false; this.loading = false;
this.showSuccessLabel = true; this.showSuccessLabel = true;
}, },
error => { error => {
this.error = error; this.error = error;
this.showErrorLabel = true; this.showErrorLabel = true;
setTimeout(() => { setTimeout(() => {
this.showErrorLabel = false; this.showErrorLabel = false;
}, 4000); }, 4000);
this.loading = false; this.loading = false;
}); });
} }
} }
} }

View File

@ -24,6 +24,7 @@ export interface Player {
name?: string; name?: string;
warId?: War; warId?: War;
kill?: number; kill?: number;
vehicle?: number;
death?: number; death?: number;
friendlyFire?: number; friendlyFire?: number;
revive?: number; revive?: number;

View File

@ -38,13 +38,13 @@ export class EditRankComponent {
ngOnInit() { ngOnInit() {
this.subscription = this.route.params this.subscription = this.route.params
.map(params => params['id']) .map(params => params['id'])
.filter(id => id != undefined) .filter(id => id != undefined)
.flatMap(id => this.rankService.getRank(id)) .flatMap(id => this.rankService.getRank(id))
.subscribe(rank => { .subscribe(rank => {
this.rank = rank; this.rank = rank;
this.imagePreviewSrc = 'resource/rank/' + this.rank._id + '.png?' + Date.now(); this.imagePreviewSrc = 'resource/rank/' + this.rank._id + '.png?' + Date.now();
}); });
} }
ngOnDestroy() { ngOnDestroy() {
@ -67,10 +67,10 @@ export class EditRankComponent {
if (this.fileList) { if (this.fileList) {
file = this.fileList[0]; file = this.fileList[0];
this.rankService.submitRank(this.rank, file) this.rankService.submitRank(this.rank, file)
.subscribe(rank => { .subscribe(rank => {
this.saved = true; this.saved = true;
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
}) })
} else { } else {
return window.alert(`Bild ist ein Pflichtfeld`); return window.alert(`Bild ist ein Pflichtfeld`);
} }
@ -80,16 +80,16 @@ export class EditRankComponent {
} }
delete this.rank['__v']; delete this.rank['__v'];
this.rankService.submitRank(this.rank, file) this.rankService.submitRank(this.rank, file)
.subscribe(rank => { .subscribe(rank => {
setTimeout(() => { setTimeout(() => {
this.imagePreviewSrc = 'resource/rank/' + this.rank._id + '.png?' + Date.now(); this.imagePreviewSrc = 'resource/rank/' + this.rank._id + '.png?' + Date.now();
}, 300); }, 300);
fileInput.value = ''; fileInput.value = '';
this.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
} }
} }

View File

@ -36,17 +36,17 @@ export class RankListComponent implements OnInit {
this.ranks$ = this.rankService.ranks$; this.ranks$ = this.rankService.ranks$;
const paramsStream = this.route.queryParams const paramsStream = this.route.queryParams
.map(params => decodeURI(params['query'] || '')) .map(params => decodeURI(params['query'] || ''))
.do(query => this.searchTerm.setValue(query)); .do(query => this.searchTerm.setValue(query));
const searchTermStream = this.searchTerm.valueChanges const searchTermStream = this.searchTerm.valueChanges
.debounceTime(400) .debounceTime(400)
.do(query => this.adjustBrowserUrl(query)); .do(query => this.adjustBrowserUrl(query));
Observable.merge(paramsStream, searchTermStream) Observable.merge(paramsStream, searchTermStream)
.distinctUntilChanged() .distinctUntilChanged()
.switchMap(query => this.rankService.findRanks(query, this.radioModel)) .switchMap(query => this.rankService.findRanks(query, this.radioModel))
.subscribe(); .subscribe();
} }
@ -68,8 +68,8 @@ export class RankListComponent implements OnInit {
const fraction = rank.fraction === 'OPFOR' ? Fraction.OPFOR : Fraction.BLUFOR; const fraction = rank.fraction === 'OPFOR' ? Fraction.OPFOR : Fraction.BLUFOR;
if (confirm('Soll der Rang ' + rank.name + ' (' + fraction + ') wirklich gelöscht werden?')) { if (confirm('Soll der Rang ' + rank.name + ' (' + fraction + ') wirklich gelöscht werden?')) {
this.rankService.deleteRank(rank) this.rankService.deleteRank(rank)
.subscribe((res) => { .subscribe((res) => {
}) })
} }
} }

View File

@ -1,6 +1,6 @@
.overview { .overview {
width: 100%!important; width: 100% !important;
margin-left: 25px!important; margin-left: 25px !important;
} }
.decoration-preview { .decoration-preview {

View File

@ -88,16 +88,16 @@ export class RequestAwardComponent {
}; };
this.awardingService.requestAwarding(award).subscribe(() => { this.awardingService.requestAwarding(award).subscribe(() => {
this.awardingService.getUserAwardings(this.user._id) this.awardingService.getUserAwardings(this.user._id)
.subscribe(awards => { .subscribe(awards => {
this.awards = awards; this.awards = awards;
this.decoration = {_id: '0'}; this.decoration = {_id: '0'};
this.reason = previewImage.src = descriptionField.innerHTML = ''; this.reason = previewImage.src = descriptionField.innerHTML = '';
this.decoPreviewDisplay = 'none'; this.decoPreviewDisplay = 'none';
this.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
}) })
} }
} }

View File

@ -1,5 +1,5 @@
.overview { .overview {
margin-left: 25px!important; margin-left: 25px !important;
} }
.decoration-preview { .decoration-preview {

View File

@ -1,5 +1,5 @@
.overview { .overview {
margin-left: 25px!important; margin-left: 25px !important;
} }
.decoration-preview { .decoration-preview {

View File

@ -1,5 +1,5 @@
.overview { .overview {
margin-left: 25px!important; margin-left: 25px !important;
} }
.decoration-preview { .decoration-preview {

View File

@ -19,10 +19,10 @@ export class AppUserService {
getUsers() { getUsers() {
this.http.get(this.config.apiAppUserPath) this.http.get(this.config.apiAppUserPath)
.map(res => res.json()) .map(res => res.json())
.do((users) => { .do((users) => {
this.appUserStore.dispatch({type: LOAD, data: users}); this.appUserStore.dispatch({type: LOAD, data: users});
}).subscribe(_ => { }).subscribe(_ => {
}); });
return this.users$; return this.users$;
@ -30,18 +30,18 @@ export class AppUserService {
updateUser(user: AppUser) { updateUser(user: AppUser) {
return this.http.patch(this.config.apiAppUserPath + user._id, user) return this.http.patch(this.config.apiAppUserPath + user._id, user)
.map(res => res.json()) .map(res => res.json())
.do(savedUser => { .do(savedUser => {
const action = {type: EDIT, data: savedUser}; const action = {type: EDIT, data: savedUser};
this.appUserStore.dispatch(action); this.appUserStore.dispatch(action);
}); });
} }
deleteUser(user) { deleteUser(user) {
return this.http.delete(this.config.apiAppUserPath + user._id) return this.http.delete(this.config.apiAppUserPath + user._id)
.do(res => { .do(res => {
this.appUserStore.dispatch({type: REMOVE, data: user}); this.appUserStore.dispatch({type: REMOVE, data: user});
}); });
} }
} }

View File

@ -18,25 +18,25 @@ export class LoginService {
login(username: string, password: string) { login(username: string, password: string) {
return this.http.post(this.config.apiAuthenticationPath, {username: username, password: password}) return this.http.post(this.config.apiAuthenticationPath, {username: username, password: password})
.map((response: Response) => { .map((response: Response) => {
// login successful if there's a jwt token in the response // login successful if there's a jwt token in the response
let user = response.json(); let user = response.json();
if (user && user.token) { if (user && user.token) {
// store user details and jwt token in cookie // store user details and jwt token in cookie
this.cookieService.set('currentUser', JSON.stringify(user)); this.cookieService.set('currentUser', JSON.stringify(user));
if (user.permission >= 2) { if (user.permission >= 2) {
const fraction = user.squad.fraction; const fraction = user.squad.fraction;
this.awardingService.checkUnprocessedAwards(fraction); this.awardingService.checkUnprocessedAwards(fraction);
this.promotionService.checkUnconfirmedPromotions(fraction); this.promotionService.checkUnconfirmedPromotions(fraction);
} }
} }
}); });
} }
signUp(username: string, password: string, secret: string) { signUp(username: string, password: string, secret: string) {
return this.http.post(this.config.apiSignupPath, {username: username, password: password, secret: secret}) return this.http.post(this.config.apiSignupPath, {username: username, password: password, secret: secret})
.map((response: Response) => { .map((response: Response) => {
}); });
} }
logout() { logout() {

View File

@ -14,7 +14,7 @@ export class AwardingService {
getUnconfirmedAwards(fraction?: string) { getUnconfirmedAwards(fraction?: string) {
return this.http.get(this.config.apiAwardPath + '?inProgress=true&fractFilter=' + fraction) return this.http.get(this.config.apiAwardPath + '?inProgress=true&fractFilter=' + fraction)
.map(res => res.json()) .map(res => res.json())
} }
checkUnprocessedAwards(fraction?: string) { checkUnprocessedAwards(fraction?: string) {
@ -30,7 +30,7 @@ export class AwardingService {
*/ */
getUserAwardings(userId: string) { getUserAwardings(userId: string) {
return this.http.get(this.config.apiAwardPath + '?userId=' + userId) return this.http.get(this.config.apiAwardPath + '?userId=' + userId)
.map(res => res.json()) .map(res => res.json())
} }
addAwarding(award: Award) { addAwarding(award: Award) {
@ -39,7 +39,7 @@ export class AwardingService {
updateAward(award) { updateAward(award) {
return this.http.patch(this.config.apiAwardPath + '/' + award._id, award) return this.http.patch(this.config.apiAwardPath + '/' + award._id, award)
.map(res => res.json()) .map(res => res.json())
} }
requestAwarding(award: Award) { requestAwarding(award: Award) {

View File

@ -27,10 +27,10 @@ export class DecorationService {
} }
this.http.get(this.config.apiDecorationPath, searchParams) this.http.get(this.config.apiDecorationPath, searchParams)
.map(res => res.json()) .map(res => res.json())
.do((squads) => { .do((squads) => {
this.decorationStore.dispatch({type: LOAD, data: squads}); this.decorationStore.dispatch({type: LOAD, data: squads});
}).subscribe(_ => { }).subscribe(_ => {
}); });
return this.decorations$; return this.decorations$;
@ -38,7 +38,7 @@ export class DecorationService {
getDecoration(id: number | string): Observable<Decoration> { getDecoration(id: number | string): Observable<Decoration> {
return this.http.get(this.config.apiDecorationPath + id) return this.http.get(this.config.apiDecorationPath + id)
.map(res => res.json()); .map(res => res.json());
} }
/** /**
@ -79,19 +79,19 @@ export class DecorationService {
}); });
return this.http.request(requestUrl, options) return this.http.request(requestUrl, options)
.map(res => res.json()) .map(res => res.json())
.do(savedDecoration => { .do(savedDecoration => {
const action = {type: accessType, data: savedDecoration}; const action = {type: accessType, data: savedDecoration};
this.decorationStore.dispatch(action); this.decorationStore.dispatch(action);
}); });
} }
deleteDecoration(decoration: Decoration) { deleteDecoration(decoration: Decoration) {
return this.http.delete(this.config.apiDecorationPath + decoration._id) return this.http.delete(this.config.apiDecorationPath + decoration._id)
.do(res => { .do(res => {
this.decorationStore.dispatch({type: REMOVE, data: decoration}); this.decorationStore.dispatch({type: REMOVE, data: decoration});
}); });
} }
} }

View File

@ -14,7 +14,7 @@ export class PromotionService {
getUnconfirmedPromotions(fraction?: string) { getUnconfirmedPromotions(fraction?: string) {
return this.http.get(this.config.apiPromotionPath + '?inProgress=true&fractFilter=' + fraction) return this.http.get(this.config.apiPromotionPath + '?inProgress=true&fractFilter=' + fraction)
.map(res => res.json()) .map(res => res.json())
} }
checkUnconfirmedPromotions(fraction?: string) { checkUnconfirmedPromotions(fraction?: string) {
@ -27,7 +27,7 @@ export class PromotionService {
getSquadPromotions(squadId: string) { getSquadPromotions(squadId: string) {
return this.http.get(this.config.apiPromotionPath + '?squadId=' + squadId) return this.http.get(this.config.apiPromotionPath + '?squadId=' + squadId)
.map(res => res.json()) .map(res => res.json())
} }
requestPromotion(promotion: Promotion) { requestPromotion(promotion: Promotion) {
@ -36,7 +36,7 @@ export class PromotionService {
updatePromotion(promotion) { updatePromotion(promotion) {
return this.http.patch(this.config.apiPromotionPath + '/' + promotion._id, promotion) return this.http.patch(this.config.apiPromotionPath + '/' + promotion._id, promotion)
.map(res => res.json()) .map(res => res.json())
} }
deletePromotion(promotionId) { deletePromotion(promotionId) {

View File

@ -28,10 +28,10 @@ export class RankService {
} }
this.http.get(this.config.apiRankPath, searchParams) this.http.get(this.config.apiRankPath, searchParams)
.map(res => res.json()) .map(res => res.json())
.do((ranks) => { .do((ranks) => {
this.rankStore.dispatch({type: LOAD, data: ranks}); this.rankStore.dispatch({type: LOAD, data: ranks});
}).subscribe(_ => { }).subscribe(_ => {
}); });
return this.ranks$; return this.ranks$;
@ -39,7 +39,7 @@ export class RankService {
getRank(id: number | string): Observable<Decoration> { getRank(id: number | string): Observable<Decoration> {
return this.http.get(this.config.apiRankPath + id) return this.http.get(this.config.apiRankPath + id)
.map(res => res.json()); .map(res => res.json());
} }
/** /**
@ -80,14 +80,14 @@ export class RankService {
}); });
return this.http.request(requestUrl, options) return this.http.request(requestUrl, options)
.map(res => res.json()) .map(res => res.json())
.do(savedRank => { .do(savedRank => {
const action = {type: accessType, data: savedRank}; const action = {type: accessType, data: savedRank};
// leave some time to save image file before accessing it through listview // leave some time to save image file before accessing it through listview
setTimeout(() => { setTimeout(() => {
this.rankStore.dispatch(action); this.rankStore.dispatch(action);
}, 300); }, 300);
}); });
} }
/** /**
@ -103,11 +103,11 @@ export class RankService {
}); });
return this.http.request(this.config.apiRankPath + rank._id, options) return this.http.request(this.config.apiRankPath + rank._id, options)
.map(res => res.json()) .map(res => res.json())
.do(savedRank => { .do(savedRank => {
const action = {type: EDIT, data: savedRank}; const action = {type: EDIT, data: savedRank};
this.rankStore.dispatch(action); this.rankStore.dispatch(action);
}); });
} }
/** /**
@ -125,18 +125,18 @@ export class RankService {
// provide token as query value, because there is no actual body // provide token as query value, because there is no actual body
// and x-access-token is ignored in multipart request // and x-access-token is ignored in multipart request
return this.http.patch(this.config.apiRankPath + rankId, formData) return this.http.patch(this.config.apiRankPath + rankId, formData)
.map(res => res.json()) .map(res => res.json())
.do(savedDecoration => { .do(savedDecoration => {
const action = {type: EDIT, data: savedDecoration}; const action = {type: EDIT, data: savedDecoration};
this.rankStore.dispatch(action); this.rankStore.dispatch(action);
}); });
} }
deleteRank(rank: Rank) { deleteRank(rank: Rank) {
return this.http.delete(this.config.apiRankPath + rank._id) return this.http.delete(this.config.apiRankPath + rank._id)
.do(res => { .do(res => {
this.rankStore.dispatch({type: REMOVE, data: rank}); this.rankStore.dispatch({type: REMOVE, data: rank});
}); });
} }

View File

@ -24,10 +24,10 @@ export class SquadService {
searchParams.append('fractFilter', fractionFilter); searchParams.append('fractFilter', fractionFilter);
this.http.get(this.config.apiSquadPath, searchParams) this.http.get(this.config.apiSquadPath, searchParams)
.map(res => res.json()) .map(res => res.json())
.do((squads) => { .do((squads) => {
this.squadStore.dispatch({type: LOAD, data: squads}); this.squadStore.dispatch({type: LOAD, data: squads});
}).subscribe(_ => { }).subscribe(_ => {
}); });
return this.squads$; return this.squads$;
@ -36,7 +36,7 @@ export class SquadService {
getSquad(id: number | string): Observable<Squad> { getSquad(id: number | string): Observable<Squad> {
return this.http.get(this.config.apiSquadPath + id) return this.http.get(this.config.apiSquadPath + id)
.map(res => res.json()); .map(res => res.json());
} }
@ -77,18 +77,18 @@ export class SquadService {
}); });
return this.http.request(requestUrl, options) return this.http.request(requestUrl, options)
.map(res => res.json()) .map(res => res.json())
.do(savedSquad => { .do(savedSquad => {
const action = {type: accessType, data: savedSquad}; const action = {type: accessType, data: savedSquad};
this.squadStore.dispatch(action); this.squadStore.dispatch(action);
}); });
} }
deleteSquad(squad: Squad) { deleteSquad(squad: Squad) {
return this.http.delete(this.config.apiSquadPath + squad._id) return this.http.delete(this.config.apiSquadPath + squad._id)
.do(res => { .do(res => {
this.squadStore.dispatch({type: REMOVE, data: squad}); this.squadStore.dispatch({type: REMOVE, data: squad});
}); });
} }
} }

View File

@ -31,46 +31,47 @@ export class UserService {
searchParams.append('limit', limit); searchParams.append('limit', limit);
searchParams.append('offset', offset); searchParams.append('offset', offset);
this.http.get(this.config.apiUserPath, searchParams) this.http.get(this.config.apiUserPath, searchParams)
.do((res) => { .do((res) => {
let headerCount = parseInt(res.headers.get('x-total-count')); let headerCount = parseInt(res.headers.get('x-total-count'));
if (headerCount) { if (headerCount) {
this.totalCount = headerCount; this.totalCount = headerCount;
} }
}).map(res => res.json()).do((users) => { }).map(res => res.json()).do((users) => {
this.userStore.dispatch({type: action, data: users}); this.userStore.dispatch({type: action, data: users});
}).subscribe(_ => {}); }).subscribe(_ => {
});
return this.users$; return this.users$;
} }
getUser(_id: number | string): Observable<User> { getUser(_id: number | string): Observable<User> {
return this.http.get(this.config.apiUserPath + _id) return this.http.get(this.config.apiUserPath + _id)
.map(res => res.json()); .map(res => res.json());
} }
submitUser(user) { submitUser(user) {
return this.http.post(this.config.apiUserPath, user) return this.http.post(this.config.apiUserPath, user)
.map(res => res.json()) .map(res => res.json())
.do(savedUser => { .do(savedUser => {
const action = {type: ADD, data: savedUser}; const action = {type: ADD, data: savedUser};
this.userStore.dispatch(action); this.userStore.dispatch(action);
}); });
} }
updateUser(user) { updateUser(user) {
return this.http.put(this.config.apiUserPath + user._id, user) return this.http.put(this.config.apiUserPath + user._id, user)
.map(res => res.json()) .map(res => res.json())
.do(savedUser => { .do(savedUser => {
const action = {type: EDIT, data: savedUser}; const action = {type: EDIT, data: savedUser};
this.userStore.dispatch(action); this.userStore.dispatch(action);
}); });
} }
deleteUser(user) { deleteUser(user) {
return this.http.delete(this.config.apiUserPath + user._id) return this.http.delete(this.config.apiUserPath + user._id)
.do(res => { .do(res => {
this.userStore.dispatch({type: REMOVE, data: user}); this.userStore.dispatch({type: REMOVE, data: user});
}); });
} }
} }

View File

@ -12,7 +12,7 @@ export class ArmyService {
getArmy() { getArmy() {
return this.http.get(this.config.apiOverviewPath) return this.http.get(this.config.apiOverviewPath)
.map(res => res.json()); .map(res => res.json());
} }
} }

View File

@ -14,17 +14,17 @@ export class CampaignService {
getAllCampaigns() { getAllCampaigns() {
return this.http.get(this.config.apiWarPath) return this.http.get(this.config.apiWarPath)
.map(res => res.json()) .map(res => res.json())
} }
submitCampaign(campaign: Campaign) { submitCampaign(campaign: Campaign) {
return this.http.post(this.config.apiCampaignPath, campaign) return this.http.post(this.config.apiCampaignPath, campaign)
.map(res => res.json()) .map(res => res.json())
} }
deleteCampaign(id: string) { deleteCampaign(id: string) {
return this.http.delete(this.config.apiCampaignPath + '/' + id) return this.http.delete(this.config.apiCampaignPath + '/' + id)
.map(res => res.json()) .map(res => res.json())
} }
} }

View File

@ -12,28 +12,28 @@ export class LogsService {
getFullLog(warId: string) { getFullLog(warId: string) {
return this.http.get(this.config.apiLogsPath + '/' + warId) return this.http.get(this.config.apiLogsPath + '/' + warId)
.map(res => res.json()) .map(res => res.json())
} }
getBudgetLogs(warId: string, fraction = '') { getBudgetLogs(warId: string, fraction = '') {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('fraction', fraction); params.append('fraction', fraction);
return this.http.get(this.config.apiLogsPath + '/' + warId + '/budget', params) return this.http.get(this.config.apiLogsPath + '/' + warId + '/budget', params)
.map(res => res.json()) .map(res => res.json())
} }
getRespawnLogs(warId: string, playerName = '') { getRespawnLogs(warId: string, playerName = '') {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('player', playerName); params.append('player', playerName);
return this.http.get(this.config.apiLogsPath + '/' + warId + '/respawn', params) return this.http.get(this.config.apiLogsPath + '/' + warId + '/respawn', params)
.map(res => res.json()) .map(res => res.json())
} }
getPointsLogs(warId: string, fraction = '') { getPointsLogs(warId: string, fraction = '') {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('fraction', fraction); params.append('fraction', fraction);
return this.http.get(this.config.apiLogsPath + '/' + warId + '/points', params) return this.http.get(this.config.apiLogsPath + '/' + warId + '/points', params)
.map(res => res.json()) .map(res => res.json())
} }
getReviveLogs(warId: string, medicName = '', patientName = '', fraction = '', stabilizedOnly = false, reviveOnly = false) { getReviveLogs(warId: string, medicName = '', patientName = '', fraction = '', stabilizedOnly = false, reviveOnly = false) {
@ -44,7 +44,7 @@ export class LogsService {
params.append('stabilized', stabilizedOnly ? 'true' : ''); params.append('stabilized', stabilizedOnly ? 'true' : '');
params.append('revive', reviveOnly ? 'true' : ''); params.append('revive', reviveOnly ? 'true' : '');
return this.http.get(this.config.apiLogsPath + '/' + warId + '/revive', params) return this.http.get(this.config.apiLogsPath + '/' + warId + '/revive', params)
.map(res => res.json()) .map(res => res.json())
} }
getKillLogs(warId: string, shooterName = '', targetName = '', fraction = '', friendlyFireOnly = false, notFriendlyFireOnly = false) { getKillLogs(warId: string, shooterName = '', targetName = '', fraction = '', friendlyFireOnly = false, notFriendlyFireOnly = false) {
@ -55,7 +55,7 @@ export class LogsService {
params.append('friendlyFire', friendlyFireOnly ? 'true' : ''); params.append('friendlyFire', friendlyFireOnly ? 'true' : '');
params.append('noFriendlyFire', notFriendlyFireOnly ? 'true' : ''); params.append('noFriendlyFire', notFriendlyFireOnly ? 'true' : '');
return this.http.get(this.config.apiLogsPath + '/' + warId + '/kills', params) return this.http.get(this.config.apiLogsPath + '/' + warId + '/kills', params)
.map(res => res.json()) .map(res => res.json())
} }
getTransportLogs(warId: string, driverName = '', passengerName = '', fraction = '') { getTransportLogs(warId: string, driverName = '', passengerName = '', fraction = '') {
@ -64,7 +64,7 @@ export class LogsService {
params.append('passenger', passengerName); params.append('passenger', passengerName);
params.append('fraction', fraction); params.append('fraction', fraction);
return this.http.get(this.config.apiLogsPath + '/' + warId + '/transport', params) return this.http.get(this.config.apiLogsPath + '/' + warId + '/transport', params)
.map(res => res.json()) .map(res => res.json())
} }
getFlagLogs(warId: string, playerName = '', fraction = '', captureOnly = false, defendOnly = false) { getFlagLogs(warId: string, playerName = '', fraction = '', captureOnly = false, defendOnly = false) {
@ -74,7 +74,7 @@ export class LogsService {
params.append('capture', captureOnly ? 'true' : ''); params.append('capture', captureOnly ? 'true' : '');
params.append('defend', defendOnly ? 'true' : ''); params.append('defend', defendOnly ? 'true' : '');
return this.http.get(this.config.apiLogsPath + '/' + warId + '/flag', params) return this.http.get(this.config.apiLogsPath + '/' + warId + '/flag', params)
.map(res => res.json()) .map(res => res.json())
} }
} }

View File

@ -11,12 +11,12 @@ export class PlayerService {
getCampaignPlayer(campaignId: string, playerName: string) { getCampaignPlayer(campaignId: string, playerName: string) {
return this.http.get(this.config.apiPlayersPath + '/single/' + campaignId + '/' + playerName) return this.http.get(this.config.apiPlayersPath + '/single/' + campaignId + '/' + playerName)
.map(res => res.json()) .map(res => res.json())
} }
getCampaignHighscore(campaignId: string) { getCampaignHighscore(campaignId: string) {
return this.http.get(this.config.apiPlayersPath + '/ranking/' + campaignId) return this.http.get(this.config.apiPlayersPath + '/ranking/' + campaignId)
.map(res => res.json()) .map(res => res.json())
} }
} }

View File

@ -12,7 +12,7 @@ export class WarService {
getWar(warId: string) { getWar(warId: string) {
return this.http.get(this.config.apiWarPath + '/' + warId) return this.http.get(this.config.apiWarPath + '/' + warId)
.map(res => res.json()) .map(res => res.json())
} }
@ -30,12 +30,12 @@ export class WarService {
} }
return this.http.post(this.config.apiWarPath, body) return this.http.post(this.config.apiWarPath, body)
.map(res => res.json()) .map(res => res.json())
} }
deleteWar(id: string) { deleteWar(id: string) {
return this.http.delete(this.config.apiWarPath + '/' + id) return this.http.delete(this.config.apiWarPath + '/' + id)
.map(res => res.json()) .map(res => res.json())
} }
} }

View File

@ -38,13 +38,13 @@ export class EditSquadComponent {
ngOnInit() { ngOnInit() {
this.subscription = this.route.params this.subscription = this.route.params
.map(params => params['id']) .map(params => params['id'])
.filter(id => id != undefined) .filter(id => id != undefined)
.flatMap(id => this.squadService.getSquad(id)) .flatMap(id => this.squadService.getSquad(id))
.subscribe(squad => { .subscribe(squad => {
this.squad = squad; this.squad = squad;
this.imagePreviewSrc = 'resource/squad/' + this.squad._id + '.png?' + Date.now(); this.imagePreviewSrc = 'resource/squad/' + this.squad._id + '.png?' + Date.now();
}); });
} }
ngOnDestroy() { ngOnDestroy() {
@ -67,10 +67,10 @@ export class EditSquadComponent {
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(rank => { .subscribe(rank => {
this.saved = true; this.saved = true;
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
}) })
} else { } else {
return window.alert(`Bild ist ein Pflichtfeld`); return window.alert(`Bild ist ein Pflichtfeld`);
} }
@ -80,16 +80,16 @@ export class EditSquadComponent {
} }
delete this.squad['__v']; delete this.squad['__v'];
this.squadService.submitSquad(this.squad, file) this.squadService.submitSquad(this.squad, file)
.subscribe(rank => { .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.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
} }
} }

View File

@ -36,17 +36,17 @@ export class SquadListComponent implements OnInit {
this.squads$ = this.squadService.squads$; this.squads$ = this.squadService.squads$;
const paramsStream = this.route.queryParams const paramsStream = this.route.queryParams
.map(params => decodeURI(params['query'] || '')) .map(params => decodeURI(params['query'] || ''))
.do(query => this.searchTerm.setValue(query)); .do(query => this.searchTerm.setValue(query));
const searchTermStream = this.searchTerm.valueChanges const searchTermStream = this.searchTerm.valueChanges
.debounceTime(400) .debounceTime(400)
.do(query => this.adjustBrowserUrl(query)); .do(query => this.adjustBrowserUrl(query));
Observable.merge(paramsStream, searchTermStream) Observable.merge(paramsStream, searchTermStream)
.distinctUntilChanged() .distinctUntilChanged()
.switchMap(query => this.squadService.findSquads(query, this.radioModel)) .switchMap(query => this.squadService.findSquads(query, this.radioModel))
.subscribe(); .subscribe();
} }
@ -64,8 +64,8 @@ export class SquadListComponent implements OnInit {
const fraction = squad.fraction === 'OPFOR' ? Fraction.OPFOR : Fraction.BLUFOR; const fraction = squad.fraction === 'OPFOR' ? Fraction.OPFOR : Fraction.BLUFOR;
if (confirm('Soll das Squad "' + squad.name + '" (' + fraction + ') wirklich gelöscht werden?')) { if (confirm('Soll das Squad "' + squad.name + '" (' + fraction + ') wirklich gelöscht werden?')) {
this.squadService.deleteSquad(squad) this.squadService.deleteSquad(squad)
.subscribe((res) => { .subscribe((res) => {
}) })
} }
} }

View File

@ -81,6 +81,26 @@
</ngx-charts-line-chart> </ngx-charts-line-chart>
</div> </div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="vehicleKillData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="killRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisVehicleKill"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container"> <div class="chart-container">
<ngx-charts-line-chart <ngx-charts-line-chart
[results]="deathData" [results]="deathData"

View File

@ -25,6 +25,7 @@ export class CampaignPlayerDetailComponent {
sumData: any[] = []; sumData: any[] = [];
killData: any[] = []; killData: any[] = [];
friendlyFireData: any[] = []; friendlyFireData: any[] = [];
vehicleKillData: any[] = [];
deathData: any[] = []; deathData: any[] = [];
respawnData: any[] = []; respawnData: any[] = [];
reviveData: any[] = []; reviveData: any[] = [];
@ -32,6 +33,7 @@ export class CampaignPlayerDetailComponent {
yAxisKill = 'Kills'; yAxisKill = 'Kills';
yAxisFriendlyFire = 'FriendlyFire'; yAxisFriendlyFire = 'FriendlyFire';
yAxisVehicleKill = 'Farzeug-Kills';
yAxisDeath = 'Tode'; yAxisDeath = 'Tode';
yAxisRespawn = 'Respawn'; yAxisRespawn = 'Respawn';
yAxisRevive = 'Revive'; yAxisRevive = 'Revive';
@ -49,6 +51,7 @@ export class CampaignPlayerDetailComponent {
showRefLines = true; showRefLines = true;
showRefLabels = true; showRefLabels = true;
killRefLines = []; killRefLines = [];
vehicleKillRefLines = [];
deathRefLines = []; deathRefLines = [];
captureRefLines = []; captureRefLines = [];
friendlyFireRefLines = []; friendlyFireRefLines = [];
@ -67,6 +70,7 @@ export class CampaignPlayerDetailComponent {
totalKills; totalKills;
totalFriendlyFire; totalFriendlyFire;
totalVehicleKills;
totalDeath; totalDeath;
totalRespawn; totalRespawn;
totalRevive; totalRevive;
@ -84,49 +88,54 @@ export class CampaignPlayerDetailComponent {
ngOnInit() { ngOnInit() {
this.playerService.getCampaignPlayer(this.campaignId, encodeURIComponent(this.playerName)) this.playerService.getCampaignPlayer(this.campaignId, encodeURIComponent(this.playerName))
.subscribe(campaignPlayer => { .subscribe(campaignPlayer => {
this.campaignPlayer = campaignPlayer; this.campaignPlayer = campaignPlayer;
this.killData = this.assignData(this.yAxisKill, "kill"); this.killData = this.assignData(this.yAxisKill, "kill");
this.friendlyFireData = this.assignData(this.yAxisFriendlyFire, "friendlyFire"); this.vehicleKillData = this.assignData(this.yAxisVehicleKill, "vehicle");
this.deathData = this.assignData(this.yAxisDeath, "death"); this.friendlyFireData = this.assignData(this.yAxisFriendlyFire, "friendlyFire");
this.respawnData = this.assignData(this.yAxisRespawn, "respawn"); this.deathData = this.assignData(this.yAxisDeath, "death");
this.reviveData = this.assignData(this.yAxisRevive, "revive"); this.respawnData = this.assignData(this.yAxisRespawn, "respawn");
this.captureData = this.assignData(this.yAxisCapture, "flagTouch"); this.reviveData = this.assignData(this.yAxisRevive, "revive");
this.captureData = this.assignData(this.yAxisCapture, "flagTouch");
this.kdRatio = parseFloat((this.totalKills / (this.totalDeath === 0 ? 1 : this.totalDeath)).toFixed(2)); this.kdRatio = parseFloat((this.totalKills / (this.totalDeath === 0 ? 1 : this.totalDeath)).toFixed(2));
if (this.kdRatio > 1) this.maxKd = this.kdRatio * 1.7; if (this.kdRatio > 1) this.maxKd = this.kdRatio * 1.7;
this.respawnDeathRatio = parseFloat((this.totalRespawn / (this.totalDeath === 0 ? 1 : this.totalDeath)).toFixed(2)); this.respawnDeathRatio = parseFloat((this.totalRespawn / (this.totalDeath === 0 ? 1 : this.totalDeath)).toFixed(2));
this.sumData = [ this.sumData = [
{ {
name: this.yAxisKill, name: this.yAxisKill,
value: this.totalKills value: this.totalKills
}, },
{ {
name: this.yAxisFriendlyFire, name: this.yAxisFriendlyFire,
value: this.totalFriendlyFire value: this.totalFriendlyFire
}, },
{ {
name: this.yAxisDeath, name: this.yAxisVehicleKill,
value: this.totalDeath value: this.totalVehicleKills
}, },
{ {
name: this.yAxisRespawn, name: this.yAxisDeath,
value: this.totalRespawn value: this.totalDeath
}, },
{ {
name: this.yAxisRevive, name: this.yAxisRespawn,
value: this.totalRevive value: this.totalRespawn
}, },
{ {
name: this.yAxisCapture, name: this.yAxisRevive,
value: this.totalCapture value: this.totalRevive
} },
]; {
name: this.yAxisCapture,
value: this.totalCapture
}
];
Object.assign(this, [this.sumData, this.killData, this.friendlyFireData, this.deathData, this.respawnData, this.reviveData, this.captureData]); Object.assign(this, [this.sumData, this.killData, this.friendlyFireData, this.vehicleKillData, this.deathData, this.respawnData, this.reviveData, this.captureData]);
}); });
} }
private assignData(label, field) { private assignData(label, field) {
@ -138,12 +147,12 @@ export class CampaignPlayerDetailComponent {
let total = 0; let total = 0;
for (let i = 0; i < playerLength; i++) { for (let i = 0; i < playerLength; i++) {
const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date); const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date);
const warKills = this.campaignPlayer.players[i][field]; const value = this.campaignPlayer.players[i][field];
killObj.series.push({ killObj.series.push({
name: warDateString, name: warDateString,
value: warKills value: value
}); });
total += warKills; total += value;
} }
switch (field) { switch (field) {
case 'kill': case 'kill':
@ -154,6 +163,10 @@ export class CampaignPlayerDetailComponent {
this.friendlyFireRefLines.push({value: total / playerLength, name: this.avgLabel}); this.friendlyFireRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalFriendlyFire = total; this.totalFriendlyFire = total;
break; break;
case 'vehicle':
this.vehicleKillRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalVehicleKills = total;
break;
case 'death': case 'death':
this.deathRefLines.push({value: total / playerLength, name: this.avgLabel}); this.deathRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalDeath = total; this.totalDeath = total;

View File

@ -27,13 +27,13 @@ export class CampaignSubmitComponent {
saveCampaign() { saveCampaign() {
this.campaignService.submitCampaign(this.campaign) this.campaignService.submitCampaign(this.campaign)
.subscribe(campaign => { .subscribe(campaign => {
this.router.navigate(['../overview/' + campaign._id], {relativeTo: this.route}); this.router.navigate(['../overview/' + campaign._id], {relativeTo: this.route});
}, },
error => { error => {
this.error = error._body.error.message; this.error = error._body.error.message;
this.showErrorLabel = true; this.showErrorLabel = true;
}); });
} }
cancel() { cancel() {

View File

@ -18,12 +18,10 @@ ngx-datatable {
float: left; float: left;
border: solid #dfdfdf 1px; border: solid #dfdfdf 1px;
border-radius: 10px 10px 2px 2px; border-radius: 10px 10px 2px 2px;
border-right-style: none;
border-top-style: none;
} }
:host /deep/ .datatable-header { :host /deep/ .datatable-header {
width: 350px!important; width: 350px !important;
background: #222222; background: #222222;
font-weight: 700; font-weight: 700;
border-radius: 10px 10px 0 0; border-radius: 10px 10px 0 0;
@ -74,5 +72,4 @@ ngx-datatable {
background: #4b4b4b; background: #4b4b4b;
-webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.5); -webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.5);
} }
/* Table Scrollbar END */ /* Table Scrollbar END */

View File

@ -36,6 +36,48 @@
<ngx-datatable-column [width]="valueColWidth" name="Kills" prop="kill"></ngx-datatable-column> <ngx-datatable-column [width]="valueColWidth" name="Kills" prop="kill"></ngx-datatable-column>
</ngx-datatable> </ngx-datatable>
<ngx-datatable
[rows]="players.friendlyFire"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.vehicle"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="Fahrzeug-Kills" prop="vehicle"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable <ngx-datatable
[rows]="players.death" [rows]="players.death"
[messages]="emptyMessage" [messages]="emptyMessage"
@ -78,27 +120,6 @@
<ngx-datatable-column [width]="valueColWidth" name="Respawn" prop="respawn"></ngx-datatable-column> <ngx-datatable-column [width]="valueColWidth" name="Respawn" prop="respawn"></ngx-datatable-column>
</ngx-datatable> </ngx-datatable>
<ngx-datatable
[rows]="players.friendlyFire"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable <ngx-datatable
[rows]="players.revive" [rows]="players.revive"
[messages]="emptyMessage" [messages]="emptyMessage"

View File

@ -22,7 +22,7 @@ export class StatisticHighScoreComponent {
searchTerm = new FormControl(); searchTerm = new FormControl();
players : Player = {}; players: Player = {};
playersStored = {}; playersStored = {};
@ -50,28 +50,28 @@ export class StatisticHighScoreComponent {
ngOnInit() { ngOnInit() {
this.route.params this.route.params
.map(params => params['id']) .map(params => params['id'])
.subscribe((id) => { .subscribe((id) => {
this.id = id; this.id = id;
if (this.campaignService.campaigns) { if (this.campaignService.campaigns) {
this.initData(); this.initData();
} else { } else {
this.campaignService.getAllCampaigns().subscribe(campaigns => { this.campaignService.getAllCampaigns().subscribe(campaigns => {
this.initData() this.initData()
}) })
} }
}); });
const searchTermStream = this.searchTerm.valueChanges.debounceTime(400); const searchTermStream = this.searchTerm.valueChanges.debounceTime(400);
Observable.merge(searchTermStream) Observable.merge(searchTermStream)
.distinctUntilChanged().map(query => this.filterPlayers()) .distinctUntilChanged().map(query => this.filterPlayers())
.subscribe(); .subscribe();
} }
initData() { initData() {
this.title = this.campaignService.campaigns this.title = this.campaignService.campaigns
.filter(camp => camp._id === this.id).pop().title; .filter(camp => camp._id === this.id).pop().title;
this.playerService.getCampaignHighscore(this.id).subscribe(players => { this.playerService.getCampaignHighscore(this.id).subscribe(players => {
this.players = players; this.players = players;
@ -86,6 +86,7 @@ export class StatisticHighScoreComponent {
this.players = { this.players = {
kill: this.filterPlayerAttribute('kill'), kill: this.filterPlayerAttribute('kill'),
friendlyFire: this.filterPlayerAttribute('friendlyFire'), friendlyFire: this.filterPlayerAttribute('friendlyFire'),
vehicle: this.filterPlayerAttribute('vehicle'),
death: this.filterPlayerAttribute('death'), death: this.filterPlayerAttribute('death'),
respawn: this.filterPlayerAttribute('respawn'), respawn: this.filterPlayerAttribute('respawn'),
revive: this.filterPlayerAttribute('revive'), revive: this.filterPlayerAttribute('revive'),

View File

@ -43,17 +43,17 @@ export class StatisticOverviewComponent {
ngOnInit() { ngOnInit() {
this.route.params this.route.params
.map(params => params['id']) .map(params => params['id'])
.subscribe((id) => { .subscribe((id) => {
this.id = id; this.id = id;
if (this.campaignService.campaigns) { if (this.campaignService.campaigns) {
this.initWars(this.campaignService.campaigns); this.initWars(this.campaignService.campaigns);
} else { } else {
this.campaignService.getAllCampaigns().subscribe(campaigns => { this.campaignService.getAllCampaigns().subscribe(campaigns => {
this.initWars(campaigns); this.initWars(campaigns);
}) })
} }
}); });
} }
initWars(campaigns) { initWars(campaigns) {

View File

@ -28,6 +28,8 @@
btnRadio="{{labelKill}}">{{labelKill}}</label> btnRadio="{{labelKill}}">{{labelKill}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel" <label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"
btnRadio="{{labelFriendlyFire}}">{{labelFriendlyFire}}</label> btnRadio="{{labelFriendlyFire}}">{{labelFriendlyFire}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"
btnRadio="{{labelVehicle}}">{{labelVehicle}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel" <label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"
btnRadio="{{labelRevive}}">{{labelRevive}}</label> btnRadio="{{labelRevive}}">{{labelRevive}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel" <label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"

View File

@ -34,6 +34,7 @@ export class FractionStatsComponent {
tmpBudgetData; tmpBudgetData;
tmpKillData; tmpKillData;
tmpFrienlyFireData; tmpFrienlyFireData;
tmpVehicleData;
tmpTransportData; tmpTransportData;
tmpReviveData; tmpReviveData;
tmpStabilizeData; tmpStabilizeData;
@ -47,6 +48,7 @@ export class FractionStatsComponent {
labelBudget = 'Budget'; labelBudget = 'Budget';
labelKill = 'Kills'; labelKill = 'Kills';
labelFriendlyFire = 'FriendlyFire'; labelFriendlyFire = 'FriendlyFire';
labelVehicle = 'Fahrzeug-Kills';
labelTransport = 'Lufttransport'; labelTransport = 'Lufttransport';
labelRevive = 'Revive'; labelRevive = 'Revive';
labelStabilize = 'Stabilisiert'; labelStabilize = 'Stabilisiert';
@ -111,6 +113,10 @@ export class FractionStatsComponent {
this.initKillData(); this.initKillData();
this.lineChartData = this.tmpFrienlyFireData; this.lineChartData = this.tmpFrienlyFireData;
break; break;
case this.labelVehicle:
this.initVehicleData();
this.lineChartData = this.tmpVehicleData;
break;
case this.labelRevive: case this.labelRevive:
this.initRevive(); this.initRevive();
this.lineChartData = this.tmpReviveData; this.lineChartData = this.tmpReviveData;
@ -250,6 +256,30 @@ export class FractionStatsComponent {
this.initialized.revive = true; this.initialized.revive = true;
} }
initVehicleData() {
if (this.initialized.vehicle) {
return;
}
let vehicleKillCountBlufor = 0, vehicleKillCountOpfor = 0;
for (const {transportEntry: vehicleEntry, index} of this.logData.vehicle.map((transportEntry, index) => ({
transportEntry,
index
}))) {
const vehicleEntryDate = new Date(vehicleEntry.time);
if (vehicleEntry.fraction === 'BLUFOR') {
vehicleKillCountBlufor++;
} else {
vehicleKillCountOpfor++;
}
if (this.isTwoMinutesAhead(vehicleEntryDate, this.tmpVehicleData) || index === this.logData.vehicle.length - 1) {
this.tmpVehicleData[0].series.push(ChartUtils.getSeriesEntry(vehicleEntryDate, vehicleKillCountBlufor));
this.tmpVehicleData[1].series.push(ChartUtils.getSeriesEntry(vehicleEntryDate, vehicleKillCountOpfor));
}
}
this.addFinalTimeData(this.tmpVehicleData);
this.initialized.vehicle = true;
}
initTransportData() { initTransportData() {
if (this.initialized.transport) { if (this.initialized.transport) {
return; return;
@ -272,7 +302,6 @@ export class FractionStatsComponent {
} }
this.addFinalTimeData(this.tmpTransportData); this.addFinalTimeData(this.tmpTransportData);
this.initialized.transport = true; this.initialized.transport = true;
} }
initFlagHoldData() { initFlagHoldData() {
@ -308,12 +337,13 @@ export class FractionStatsComponent {
this.tmpBudgetData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpBudgetData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpKillData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpKillData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpFrienlyFireData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpFrienlyFireData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpVehicleData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpTransportData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpTransportData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpReviveData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpReviveData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpStabilizeData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpStabilizeData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpFlagCaptureData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpFlagCaptureData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
[this.tmpKillData, this.tmpFrienlyFireData, this.tmpReviveData, this.tmpStabilizeData, this.tmpTransportData].forEach(tmp => { [this.tmpKillData, this.tmpFrienlyFireData, this.tmpVehicleData, this.tmpReviveData, this.tmpStabilizeData, this.tmpTransportData].forEach(tmp => {
[0, 1].forEach(index => { [0, 1].forEach(index => {
tmp[index].series.push(ChartUtils.getSeriesEntry(this.startDateObj, 0)); tmp[index].series.push(ChartUtils.getSeriesEntry(this.startDateObj, 0));
}) })

View File

@ -11,7 +11,7 @@ ngx-datatable {
} }
:host /deep/ .datatable-header { :host /deep/ .datatable-header {
width: 1100px!important; width: 1100px !important;
background: #222222; background: #222222;
font-weight: 700; font-weight: 700;
border-radius: 10px 10px 0 0; border-radius: 10px 10px 0 0;

View File

@ -25,18 +25,22 @@
</ngx-datatable-column> </ngx-datatable-column>
<ngx-datatable-column [width]="90" name="Kills" prop="kill"></ngx-datatable-column> <ngx-datatable-column [width]="90" name="Kills" prop="kill"></ngx-datatable-column>
<ngx-datatable-column [width]="104" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column> <ngx-datatable-column [width]="104" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column>
<ngx-datatable-column [width]="90" name="Fahrzeug" prop="vehicle"></ngx-datatable-column>
<ngx-datatable-column [width]="80" name="Revive" prop="revive"></ngx-datatable-column> <ngx-datatable-column [width]="80" name="Revive" prop="revive"></ngx-datatable-column>
<ngx-datatable-column [width]="100" name="Eroberung" prop="flagTouch"></ngx-datatable-column> <ngx-datatable-column [width]="100" name="Eroberung" prop="flagTouch"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="Tod" prop="death"></ngx-datatable-column> <ngx-datatable-column [width]="70" name="Tod" prop="death"></ngx-datatable-column>
<ngx-datatable-column [width]="90" name="Respawn" prop="respawn"></ngx-datatable-column> <ngx-datatable-column [width]="90" name="Respawn" prop="respawn"></ngx-datatable-column>
<ngx-datatable-column [width]="80" name="" prop="name"> <!--<ngx-datatable-column [width]="80" name="" prop="name">-->
<ng-template ngx-datatable-cell-template let-value="value"> <!--<ng-template ngx-datatable-cell-template let-value="value">-->
<span class="btn btn-sm btn-default in-table-btn disabled">Detail</span> <!--<span class="btn btn-sm btn-default in-table-btn disabled">Detail</span>-->
</ng-template> <!--</ng-template>-->
</ngx-datatable-column> <!--</ngx-datatable-column>-->
<ngx-datatable-column [width]="80" name="" prop="name"> <ngx-datatable-column [width]="80">
<ng-template ngx-datatable-cell-template let-value="value"> <ng-template ngx-datatable-cell-template let-row="row">
<span class="btn btn-sm btn-default in-table-btn" (click)="selectPlayerDetail(1, value)">Gesamt</span> <span class="btn btn-sm btn-default in-table-btn"
(click)="selectPlayerDetail(1, isSteamUUID(row['steamUUID']) ? row['steamUUID'] : row['name'])">
Gesamt
</span>
</ng-template> </ng-template>
</ngx-datatable-column> </ngx-datatable-column>
</ngx-datatable> </ngx-datatable>

View File

@ -1,6 +1,7 @@
import {Component, EventEmitter, SimpleChanges} from "@angular/core"; import {Component, ElementRef, EventEmitter, SimpleChanges} from "@angular/core";
import {War} from "../../../models/model-interfaces"; import {War} from "../../../models/model-interfaces";
import {Fraction} from "../../../utils/fraction.enum"; import {Fraction} from "../../../utils/fraction.enum";
import {PlayerUtils} from "../../../utils/player-utils";
@Component({ @Component({
selector: 'scoreboard', selector: 'scoreboard',
@ -17,6 +18,8 @@ export class ScoreboardComponent {
war: War; war: War;
isSteamUUID = PlayerUtils.isSteamUUID;
fractionFilterSelected: string; fractionFilterSelected: string;
cellHeight = 40; cellHeight = 40;
@ -30,19 +33,22 @@ export class ScoreboardComponent {
sortDescending: 'glyphicon glyphicon-triangle-bottom', sortDescending: 'glyphicon glyphicon-triangle-bottom',
}; };
constructor() { constructor(private elRef: ElementRef) {
} }
ngOnInit() { selectPlayerDetail(view: number, player) {
} this.playerTabSwitch.emit({
view: view,
selectPlayerDetail(view: number, playerName: string) { player: player
this.playerTabSwitch.emit({view: view, player: playerName}) })
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes.war) { if (changes.war) {
this.rows = changes.war.currentValue.players; this.rows = changes.war.currentValue.players;
this.elRef.nativeElement
.querySelector('.datatable-body')
.scrollTo(0, 0);
} }
if (changes.fractionFilterSelected) { if (changes.fractionFilterSelected) {
this.filterPlayersByFraction(this.fractionFilterSelected) this.filterPlayersByFraction(this.fractionFilterSelected)

View File

@ -19,6 +19,9 @@
} }
.nav-tabs { .nav-tabs {
width: 980px;
margin: auto;
clear: both;
border-bottom: 0; border-bottom: 0;
} }

View File

@ -50,7 +50,7 @@
</div> </div>
</div> </div>
<ul class="nav nav-tabs" style="width:980px; margin:auto"> <ul class="nav nav-tabs">
<li class="nav-item" [ngClass]="{active :tab === 0}" (click)="switchTab(0)"> <li class="nav-item" [ngClass]="{active :tab === 0}" (click)="switchTab(0)">
<a class="nav-link"><img src="../../../assets/scoreboard-btn.png"> Scoreboard</a> <a class="nav-link"><img src="../../../assets/scoreboard-btn.png"> Scoreboard</a>
</li> </li>

View File

@ -43,19 +43,19 @@ export class WarDetailComponent {
ngOnInit() { ngOnInit() {
this.route.params this.route.params
.map(params => params['id']) .map(params => params['id'])
.filter(id => id != undefined) .filter(id => id != undefined)
.flatMap(id => this.warService.getWar(id)) .flatMap(id => this.warService.getWar(id))
.subscribe(war => { .subscribe(war => {
this.war = war; this.war = war;
this.switchTab(0); this.switchTab(0);
this.fractionStatsInitialized = false; this.fractionStatsInitialized = false;
this.fractionFilterSelected = undefined; this.fractionFilterSelected = undefined;
this.playerChart = ChartUtils.getSingleDataArray(Fraction.OPFOR, war.playersOpfor, Fraction.BLUFOR, war.playersBlufor); this.playerChart = ChartUtils.getSingleDataArray(Fraction.OPFOR, war.playersOpfor, Fraction.BLUFOR, war.playersBlufor);
Object.assign(this, [this.playerChart]); Object.assign(this, [this.playerChart]);
}) })
} }
switchTab(index: number) { switchTab(index: number) {

View File

@ -78,24 +78,24 @@ export class WarListComponent implements OnInit {
deleteWar(war: War) { deleteWar(war: War) {
if (confirm('Soll die Schlacht ' + war.title + ' wirklich gelöscht werden?')) { if (confirm('Soll die Schlacht ' + war.title + ' wirklich gelöscht werden?')) {
this.warService.deleteWar(war._id) this.warService.deleteWar(war._id)
.subscribe((res) => { .subscribe((res) => {
if (this.selectedWarId === war._id) { if (this.selectedWarId === war._id) {
this.selectOverview('all'); this.selectOverview('all');
} }
this.campaigns.splice(this.campaigns.indexOf(war), 1); this.campaigns.splice(this.campaigns.indexOf(war), 1);
}) })
} }
} }
deleteCampaign(campaign) { deleteCampaign(campaign) {
if (confirm('Soll die Kampagne ' + campaign.title + ' wirklich gelöscht werden?')) { if (confirm('Soll die Kampagne ' + campaign.title + ' wirklich gelöscht werden?')) {
this.campaignService.deleteCampaign(campaign._id) this.campaignService.deleteCampaign(campaign._id)
.subscribe((res) => { .subscribe((res) => {
if (this.selectedWarId === campaign._id) { if (this.selectedWarId === campaign._id) {
this.selectOverview('all'); this.selectOverview('all');
} }
this.campaigns.splice(this.campaigns.indexOf(campaign), 1); this.campaigns.splice(this.campaigns.indexOf(campaign), 1);
}) })
} }
} }

View File

@ -52,14 +52,14 @@ export class WarSubmitComponent {
this.loading = true; this.loading = true;
this.warService.submitWar(this.war, file) this.warService.submitWar(this.war, file)
.subscribe(war => { .subscribe(war => {
this.router.navigate(['../war/' + war._id], {relativeTo: this.route}); this.router.navigate(['../war/' + war._id], {relativeTo: this.route});
}, },
error => { error => {
this.error = error._body.error.message; this.error = error._body.error.message;
this.showErrorLabel = true; this.showErrorLabel = true;
this.loading = false; this.loading = false;
}); });
} else { } else {
return window.alert(`Logfile ist ein Pflichtfeld`); return window.alert(`Logfile ist ein Pflichtfeld`);
} }

View File

@ -1,6 +1,6 @@
.overview { .overview {
overflow: hidden!important; overflow: hidden !important;
padding-top: 80px!important; padding-top: 80px !important;
width: 20%; width: 20%;
min-width: 280px; min-width: 280px;
} }

View File

@ -26,7 +26,7 @@ div.user-list-entry, a.user-list-entry {
background: lightgrey; background: lightgrey;
} }
span > a, span.glyphicon, span.icon-award{ span > a, span.glyphicon, span.icon-award {
cursor: pointer; cursor: pointer;
} }

View File

@ -40,15 +40,15 @@ export class AwardUserComponent {
}); });
this.route.params this.route.params
.map(params => params['id']) .map(params => params['id'])
.flatMap(id => this.awardingService.getUserAwardings(id)) .flatMap(id => this.awardingService.getUserAwardings(id))
.subscribe(awards => { .subscribe(awards => {
this.awards = awards; this.awards = awards;
}); });
this.route.params this.route.params
.map(params => params['id']) .map(params => params['id'])
.subscribe(id => this.userId = id) .subscribe(id => this.userId = id)
} }
@ -79,16 +79,16 @@ export class AwardUserComponent {
}; };
this.awardingService.addAwarding(award).subscribe(() => { this.awardingService.addAwarding(award).subscribe(() => {
this.awardingService.getUserAwardings(this.userId) this.awardingService.getUserAwardings(this.userId)
.subscribe(awards => { .subscribe(awards => {
this.awards = awards; this.awards = awards;
this.decoPreviewDisplay = 'none'; this.decoPreviewDisplay = 'none';
decorationField.value = undefined; decorationField.value = undefined;
reasonField.value = previewImage.src = descriptionField.innerHTML = ''; reasonField.value = previewImage.src = descriptionField.innerHTML = '';
this.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
}) })
} }
} }
@ -96,13 +96,13 @@ export class AwardUserComponent {
deleteAwarding(awardingId) { deleteAwarding(awardingId) {
this.awardingService.deleteAwarding(awardingId).subscribe((res) => { this.awardingService.deleteAwarding(awardingId).subscribe((res) => {
this.awardingService.getUserAwardings(this.userId) this.awardingService.getUserAwardings(this.userId)
.subscribe((awards) => { .subscribe((awards) => {
this.awards = awards; this.awards = awards;
this.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
}) })
} }

View File

@ -45,21 +45,21 @@ export class EditUserComponent {
ngOnInit() { ngOnInit() {
this.subscription = this.route.params this.subscription = this.route.params
.map(params => params['id']) .map(params => params['id'])
.filter(id => id != undefined) .filter(id => id != undefined)
.flatMap(id => this.userService.getUser(id)) .flatMap(id => this.userService.getUser(id))
.subscribe(user => { .subscribe(user => {
if (!user.squadId) { if (!user.squadId) {
user.squadId = "0"; user.squadId = "0";
this.ranksDisplay = 'none'; this.ranksDisplay = 'none';
} else { } else {
this.rankService.findRanks('', user.squadId.fraction).subscribe(ranks => { this.rankService.findRanks('', user.squadId.fraction).subscribe(ranks => {
this.ranks = ranks; this.ranks = ranks;
this.ranksDisplay = 'block'; this.ranksDisplay = 'block';
}); });
} }
this.user = user; this.user = user;
}); });
this.squadService.findSquads().subscribe(squads => { this.squadService.findSquads().subscribe(squads => {
this.squads = squads; this.squads = squads;
@ -92,32 +92,32 @@ export class EditUserComponent {
if (this.user._id) { if (this.user._id) {
this.userService.updateUser(updateObject) this.userService.updateUser(updateObject)
.subscribe(user => { .subscribe(user => {
if (!user.squad) { if (!user.squad) {
user.squad = '0'; user.squad = '0';
} }
this.user = user; this.user = user;
this.showSuccessLabel = true; this.showSuccessLabel = true;
setTimeout(() => { setTimeout(() => {
this.showSuccessLabel = false; this.showSuccessLabel = false;
}, 2000) }, 2000)
}) })
} else { } else {
this.userService.submitUser(updateObject) this.userService.submitUser(updateObject)
.subscribe(user => { .subscribe(user => {
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
return true; return true;
}, },
error => { error => {
// duplicated user error message // duplicated user error message
if (error._body.includes('duplicate')) { if (error._body.includes('duplicate')) {
this.error = "Benutzername existiert bereits"; this.error = "Benutzername existiert bereits";
this.showErrorLabel = true; this.showErrorLabel = true;
setTimeout(() => { setTimeout(() => {
this.showErrorLabel = false; this.showErrorLabel = false;
}, 5000); }, 5000);
} }
}) })
} }
} }

View File

@ -31,7 +31,7 @@ export class UserListComponent implements OnInit {
offset = 0; offset = 0;
limit = 20; limit = 20;
readonly fraction = Fraction; readonly fraction = Fraction;
constructor(private userService: UserService, constructor(private userService: UserService,
@ -45,17 +45,17 @@ export class UserListComponent implements OnInit {
this.users$ = this.userService.users$; this.users$ = this.userService.users$;
const paramsStream = this.route.queryParams const paramsStream = this.route.queryParams
.map(params => decodeURI(params['query'] || '')) .map(params => decodeURI(params['query'] || ''))
.do(query => this.searchTerm.setValue(query)); .do(query => this.searchTerm.setValue(query));
const searchTermStream = this.searchTerm.valueChanges const searchTermStream = this.searchTerm.valueChanges
.debounceTime(400) .debounceTime(400)
.do(query => this.adjustBrowserUrl(query)); .do(query => this.adjustBrowserUrl(query));
Observable.merge(paramsStream, searchTermStream) Observable.merge(paramsStream, searchTermStream)
.distinctUntilChanged() .distinctUntilChanged()
.switchMap(query => this.filterUsers()) .switchMap(query => this.filterUsers())
.subscribe(); .subscribe();
} }
openNewUserForm() { openNewUserForm() {
@ -76,8 +76,8 @@ export class UserListComponent implements OnInit {
deleteUser(user: User) { deleteUser(user: User) {
if (confirm('Soll der Teilnehmer "' + user.username + '" wirklich gelöscht werden?')) { if (confirm('Soll der Teilnehmer "' + user.username + '" wirklich gelöscht werden?')) {
this.userService.deleteUser(user) this.userService.deleteUser(user)
.subscribe((res) => { .subscribe((res) => {
}) })
} }
} }

View File

@ -14,7 +14,7 @@ export class ChartUtils {
}; };
} }
public static getMultiDataArray(...args: string[]) : any[] { public static getMultiDataArray(...args: string[]): any[] {
const obj = []; const obj = [];
for (let i = 0, arg; arg = args[i]; i++) { for (let i = 0, arg; arg = args[i]; i++) {
obj.push({ obj.push({

View File

@ -0,0 +1,7 @@
export class PlayerUtils {
public static isSteamUUID(input: string): boolean {
const steamUIDPattern = new RegExp("[0-9]{17}");
return steamUIDPattern.test(input)
}
}