diff --git a/api/apib/dredd/data/campaign.json b/api/apib/dredd/data/campaign.json index 2c31b97..92098d0 100644 --- a/api/apib/dredd/data/campaign.json +++ b/api/apib/dredd/data/campaign.json @@ -1,2 +1,3 @@ {"_id":{"$oid":"5abd55ea9e30a76bfef747d6"},"title":"Ein Kessel Buntes","timestamp":{"$date":"2018-03-29T21:08:58.123Z"},"updatedAt":{"$date":"2018-03-29T21:08:58.123Z"},"__v":0} {"_id":{"$oid":"5abd58989e30a76bfef747e6"},"title":"This Is The End","timestamp":{"$date":"2018-03-29T21:20:24.558Z"},"updatedAt":{"$date":"2018-03-29T21:20:24.558Z"},"__v":0} +{"_id":{"$oid":"5abd55ea9e32a76afef777d6"},"title":"Placeholder","timestamp":{"$date":"2018-03-30T21:08:58.123Z"},"updatedAt":{"$date":"2018-03-30T21:08:58.123Z"},"__v":0} diff --git a/api/apib/dredd/data/war.json b/api/apib/dredd/data/war.json index ec8d2ee..4978a30 100644 --- a/api/apib/dredd/data/war.json +++ b/api/apib/dredd/data/war.json @@ -1,2 +1,3 @@ {"_id":{"$oid":"5abf65d83fc5fa349ffd5cbb"},"playersBlufor":4,"playersOpfor":4,"budgetBlufor":4535500,"budgetOpfor":4221250,"endBudgetBlufor":997000,"endBudgetOpfor":512000,"title":"Battle #1","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-19T23:00:00.000Z"},"endDate":{"$date":"2018-03-20T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-31T10:41:28.451Z"},"updatedAt":{"$date":"2018-03-31T10:41:28.451Z"},"__v":0} {"_id":{"$oid":"5abf65ae3fc5fa349ffd5ca3"},"playersBlufor":4,"playersOpfor":4,"budgetBlufor":4535500,"budgetOpfor":4221250,"endBudgetBlufor":997000,"endBudgetOpfor":512000,"title":"Test Battle #0","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-19T23:00:00.000Z"},"endDate":{"$date":"2018-03-20T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-31T10:40:46.695Z"},"updatedAt":{"$date":"2018-03-31T10:40:46.695Z"},"__v":0} +{"_id":{"$oid":"5abf65ae2df5fa349ffd5ca3"},"playersBlufor":20,"playersOpfor":20,"budgetBlufor":4535500,"budgetOpfor":4535500,"endBudgetBlufor":0,"endBudgetOpfor":20000,"title":"Test Battle XY","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-02T23:00:00.000Z"},"endDate":{"$date":"2018-03-02T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-02T10:40:46.695Z"},"updatedAt":{"$date":"2018-03-02T10:40:46.695Z"},"__v":0} diff --git a/api/apib/dredd/populate-data.sh b/api/apib/dredd/populate-data.sh index 47dc954..3544f4d 100755 --- a/api/apib/dredd/populate-data.sh +++ b/api/apib/dredd/populate-data.sh @@ -4,7 +4,7 @@ ###-------------------------------------------------------------### ###------------------- HOW TO USE THIS FILE --------------------### ###-------------------------------------------------------------### -### 1. start express server with `npm run start-test` ### +### 1. start express server with `npm run start-api-test` ### ### 2. import data by executing script: `./populate-data.sh` ### ### 3. change data in app as you need for tests ### ### 4. export data state with: `./populate-data.sh -m save` ### diff --git a/api/apib/statistics/campaigns.apib b/api/apib/statistics/campaigns.apib index 915aa26..8b028b0 100644 --- a/api/apib/statistics/campaigns.apib +++ b/api/apib/statistics/campaigns.apib @@ -24,6 +24,25 @@ Create a new campaign + Attributes (Campaign, fixed-type) +### Update Campaign [PATCH /campaigns/{id}] + +Update a campaign, identified by its id + +**Permission: 3** + ++ Parameters + + id: `5abd55ea9e32a76afef777d6` (string, required) - unique id of campaign + ++ Request (application/json) + + + Attributes + + _id: `5abd55ea9e32a76afef777d6` (string, required) - unique id of campaign + + title: `Operation Pandora` (string, optional) - display name of the campaign + ++ Response 200 (application/json; charset=utf-8) + + + Attributes (Campaign, fixed-type) + ### Delete Campaign [DELETE /campaigns/{id}] Delete a campaign diff --git a/api/apib/statistics/wars.apib b/api/apib/statistics/wars.apib index 6ce40bb..c370a9c 100644 --- a/api/apib/statistics/wars.apib +++ b/api/apib/statistics/wars.apib @@ -76,6 +76,35 @@ Create a new war + Attributes (War, fixed-type) +### Update War [PATCH /wars/{id}] + +Update a war, identified by its id + +**Permission: 3** + ++ Parameters + + id: `5abf65ae2df5fa349ffd5ca3` (string, required) - unique id of campaign + ++ Request (application/json) + + + Attributes + + _id: `5abf65ae2df5fa349ffd5ca3` (string, required) - unique id of war + + title: `Final Touchdown` (string, optional) - display name of the war + + date: `2017-05-11T20:00:00.471Z` (string, optional) - starting date + + endDate: `2017-05-12T00:30:32.471Z` (string, optional) - end date + + ptBlufor: 5 (number, optional) - points Blufor + + ptOpfor: 5 (number, optional) - points Opfor + + playersBlufor: 20 (number, optional) - number of players Blufor + + playersOpfor: 20 (number, optional) - number of players Opfor + + budgetBlufor: 3000000 (number, optional) - start budget Blufor + + budgetOpfor: 3000000 (number, optional) - start budget Opfor + + endBudgetBlufor: 10000 (number, optional) - end budget Blufor + + endBudgetOpfor: 12000 (number, optional) - end budget Opfor + ++ Response 200 (application/json; charset=utf-8) + + + Attributes (War, fixed-type) + ### Delete War [DELETE /wars/{id}] Delete a war diff --git a/api/routes/_generic.js b/api/routes/_generic.js new file mode 100644 index 0000000..7cd0cb7 --- /dev/null +++ b/api/routes/_generic.js @@ -0,0 +1,53 @@ +'use strict'; + +// HTTP status codes by name +const codes = require('./http-codes'); + +const genericGetById = (req, res, next, modelClass) => { + modelClass.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); + } + res.locals.items = item; + return next(); + }); +}; + +const genericPatch = (req, res, next, modelClass) => { + 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. + } + + req.body.updatedAt = new Date(); + req.body.$inc = {__v: 1}; + if (req.body.hasOwnProperty('__v')) { + delete req.body.__v; + } + + // 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. + modelClass.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); + }); +}; + +exports.genericGetById = genericGetById; +exports.genericPatch = genericPatch; diff --git a/api/routes/campaigns.js b/api/routes/campaigns.js index 0cb9c85..573b74b 100644 --- a/api/routes/campaigns.js +++ b/api/routes/campaigns.js @@ -16,12 +16,14 @@ const idValidator = require('../middleware/validators').idValidator; const CampaignModel = require('../models/campaign'); const WarModel = require('../models/war'); +// util +const genericGetById = require('./_generic').genericGetById; +const genericPatch = require('./_generic').genericPatch; const campaigns = new express.Router(); // routes ********************** campaigns.route('/') - .post(apiAuthenticationMiddleware, checkMT, (req, res, next) => { const campaign = new CampaignModel(req.body); // timestamp and default are set automatically by Mongoose Schema Validation @@ -43,18 +45,11 @@ campaigns.route('/') campaigns.route('/:id') .get(idValidator, (req, res, next) => { - CampaignModel.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); - } - res.locals.items = item; - return next(); - }); + return genericGetById(req, res, next, CampaignModel); + }) + + .patch(apiAuthenticationMiddleware, checkMT, (req, res, next) => { + return genericPatch(req, res, next, CampaignModel); }) .delete((req, res, next) => { diff --git a/api/routes/decorations.js b/api/routes/decorations.js index 2302217..6bd782a 100644 --- a/api/routes/decorations.js +++ b/api/routes/decorations.js @@ -21,6 +21,9 @@ const resourceLocation = require('../middleware/resource-location').resourceLoca const DecorationModel = require('../models/decoration'); const AwardingsModel = require('../models/awarding'); +// util +const genericGetById = require('./_generic').genericGetById; + const decoration = new express.Router(); // routes ********************** @@ -81,18 +84,7 @@ decoration.route('/') decoration.route('/:id') .get(idValidator, (req, res, next) => { - DecorationModel.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); - } - res.locals.items = item; - next(); - }); + return genericGetById(req, res, next, DecorationModel); }) .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { diff --git a/api/routes/ranks.js b/api/routes/ranks.js index 390bb70..2853a34 100644 --- a/api/routes/ranks.js +++ b/api/routes/ranks.js @@ -20,6 +20,9 @@ const resourceLocation = require('../middleware/resource-location').resourceLoca // Mongoose Model using mongoDB const RankModel = require('../models/rank'); +// util +const genericGetById = require('./_generic').genericGetById; + const ranks = new express.Router(); // routes ********************** @@ -73,18 +76,7 @@ ranks.route('/') ranks.route('/:id') .get(idValidator, (req, res, next) => { - RankModel.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); - } - res.locals.items = item; - next(); - }); + return genericGetById(req, res, next, RankModel); }) .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { diff --git a/api/routes/squads.js b/api/routes/squads.js index 80582ab..8764f4f 100644 --- a/api/routes/squads.js +++ b/api/routes/squads.js @@ -20,6 +20,9 @@ const resourceLocation = require('../middleware/resource-location').resourceLoca // Mongoose Model using mongoDB const SquadModel = require('../models/squad'); +// util +const genericGetById = require('./_generic').genericGetById; + const squads = new express.Router(); // routes ********************** @@ -77,18 +80,7 @@ squads.route('/') squads.route('/:id') .get(idValidator, (req, res, next) => { - SquadModel.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); - } - res.locals.items = item; - next(); - }); + return genericGetById(req, res, next, SquadModel); }) .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { diff --git a/api/routes/wars.js b/api/routes/wars.js index 15d3e44..65b04d2 100644 --- a/api/routes/wars.js +++ b/api/routes/wars.js @@ -35,6 +35,9 @@ const LogFlagModel = require('../models/logs/flag'); const LogBudgetModel = require('../models/logs/budget'); const LogPointsModel = require('../models/logs/points'); +// util +const genericPatch = require('./_generic').genericPatch; + const wars = new express.Router(); // routes ********************** @@ -168,6 +171,10 @@ wars.route('/:id') }); }) + .patch(apiAuthenticationMiddleware, checkMT, (req, res, next) => { + return genericPatch(req, res, next, WarModel); + }) + .delete(apiAuthenticationMiddleware, checkMT, (req, res, next) => { WarModel.findByIdAndRemove(req.params.id, (err, item) => { if (err) { diff --git a/api/tools/log-parse-tool.js b/api/tools/log-parse-tool.js index 299016c..4ef4d99 100644 --- a/api/tools/log-parse-tool.js +++ b/api/tools/log-parse-tool.js @@ -106,6 +106,12 @@ const parseWarLog = (lineArray, war) => { stats.war['endBudgetBlufor'] = transformMoneyString(budg[9]); stats.war['endBudgetOpfor'] = transformMoneyString(budg[12].slice(0, -2)); stats.war.endDate = getFullTimeDate(war.date, budg[5]); + } else if (line.includes('Respawn von ')) { + /** + * RESPAWN + */ + const playerName = line.substring(line.lastIndexOf('Respawn von ') + 12, line.lastIndexOf('"')); + stats.respawn.push(getRespawnEntry(budg, playerName, war._id, war.date)); } else { stats.budget.push(getBudgetEntry(budg, war._id, war.date)); } @@ -140,13 +146,6 @@ const parseWarLog = (lineArray, war) => { } else { stats.points.push(getPointsEntry(pt, line, war._id, war.date)); } - } else if (line.includes('(Respawn)')) { - /** - * RESPAWN - */ - const resp = line.split(WHITESPACE); - const playerName = line.substring(line.lastIndexOf('Spieler:') + 9, line.lastIndexOf('- Kosten') - 1); - stats.respawn.push(getRespawnEntry(resp, playerName, war._id, war.date)); } else if (line.includes('(Revive)')) { /** * REVIVE diff --git a/api/tools/signature-tool.js b/api/tools/signature-tool.js index 8fe9b7e..86a0010 100644 --- a/api/tools/signature-tool.js +++ b/api/tools/signature-tool.js @@ -137,48 +137,7 @@ let addDecorationsAndSave = (userId, loadedImage, res, next) => { return next(err); } if (awardings.length > 0) { - // TODO: simplify this sorting hell - awardings.sort((a1, a2) => { - if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) { - if (a1.decorationId.fraction === a2.decorationId.fraction) { - if (a1.date !== a2.date) { - if (a1.date < a2.date) { - return -1; - } - if (a1.date > a2.date) { - return 1; - } - } - return 0; - } else { - if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') { - return -1; - } - if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') { - return 1; - } - } - } - if (a1.decorationId.isMedal !== a2.decorationId.isMedal) { - if (a1.decorationId.isMedal && !a2.decorationId.isMedal) { - return 1; - } - if (!a1.decorationId.isMedal && a2.decorationId.isMedal) { - return -1; - } - } - if (a1.decorationId.isMedal && a2.decorationId.isMedal) { - if (a1.date !== a2.date) { - if (a1.date < a2.date) { - return -1; - } - if (a1.date > a2.date) { - return 1; - } - } - return 0; - } - }); + awardings.sort((a1, a2) => sortAwardingsForSignature(a1, a2)); // use synchronized call to keep correct order of decorations async.eachSeries(awardings, (award, callback) => { @@ -262,5 +221,47 @@ let saveJimpImageAndCompress = (image, userId, res, next) => { }, 3000); }; +const sortAwardingsForSignature = (a1, a2) => { + if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) { + if (a1.decorationId.fraction === a2.decorationId.fraction) { + if (a1.date !== a2.date) { + if (a1.date < a2.date) { + return -1; + } + if (a1.date > a2.date) { + return 1; + } + } + return 0; + } else { + if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') { + return -1; + } + if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') { + return 1; + } + } + } + if (a1.decorationId.isMedal !== a2.decorationId.isMedal) { + if (a1.decorationId.isMedal && !a2.decorationId.isMedal) { + return 1; + } + if (!a1.decorationId.isMedal && a2.decorationId.isMedal) { + return -1; + } + } + if (a1.decorationId.isMedal && a2.decorationId.isMedal) { + if (a1.date !== a2.date) { + if (a1.date < a2.date) { + return -1; + } + if (a1.date > a2.date) { + return 1; + } + } + return 0; + } +}; + module.exports = createSignature; diff --git a/api/tools/util.js b/api/tools/util.js index 155ac34..86f699e 100644 --- a/api/tools/util.js +++ b/api/tools/util.js @@ -39,7 +39,6 @@ const timeStringToDecimal = (timeString) => { return hour + (sek / 3600); }; - const decimalToTimeString = (decimal) => { const hours = parseInt(decimal.toString().split('.')[0]); const minutesFloat = ((decimal % 1) * 3600) / 60; diff --git a/docs/infra/codestyle/cc-intellij-idea-javascript-typescript-style.xml b/docs/infra/codestyle/cc-intellij-idea-javascript-typescript-style.xml index c75b1af..185cecc 100644 --- a/docs/infra/codestyle/cc-intellij-idea-javascript-typescript-style.xml +++ b/docs/infra/codestyle/cc-intellij-idea-javascript-typescript-style.xml @@ -10,6 +10,7 @@