Compare commits

..

2 Commits

Author SHA1 Message Date
HardiReady e848ee1b66 update from master v1.6.11 2018-02-18 12:54:22 +01:00
HardiReady 45c16e2bdf Add empty view and tab switch 2017-12-16 15:10:49 +01:00
562 changed files with 23410 additions and 38354 deletions

9
.gitignore vendored
View File

@ -4,7 +4,6 @@
dist/
tmp/
etc/
server/apib/documentation.apib
# dependencies
node_modules
@ -45,11 +44,10 @@ Thumbs.db
.directory
# Internal Data
/public/
server/resource/
server/apib/dredd/data/tmp-resource
public/
mongodb-data/
resource/
backup/
!backup.sh
# System
.npm/
@ -59,3 +57,4 @@ backup/
.cache/motd.legal-displayed
.profile
.ssh/

View File

@ -1,7 +1,7 @@
# Operation Pandora Trigger Commandcenter
A [MEAN Stack](http://mean.io/) application created for [https://www.opt4.net](https://www.opt4.net) Arma3 Community
A [MEAN Application](http://mean.io/) created for [operation-pandora.com](https://www.operation-pandora.com) Arma3 Community
## Installation
@ -14,25 +14,27 @@ All steps described here are working with a Debian based Linux system
Run the installation script located in the docs folder:
```text
./docs/opt-cc-environment/3rd-party-install.sh
```
./docs/opt-cc-environment/3rd-party-install
It installs NPM, Node and MongoDB on latest versions.
In addition, it sets up the mongo deamon to start up automatically with the system.
#### Setup for Production
**NOTE:** It his highly recommended **not** to run the following steps as _root_ user!
**NOTE:** It his highly recommended not to run the following steps as _root_ user!
Before triggering the environment execution run
npm install
inside the main folder, to process all needed npm package installations for the program execution.
For production setup run the script, described in _Setup for development_, adding the parameter `prod`
```text
./docs/opt-cc-environment/3rd-party-install.sh prod
```
./docs/opt-cc-environment/3rd-party-install prod
This adds the [`pm2` process manager](http://pm2.keymetrics.io/) to be installed and start the _opt-cc_ server as `pm2` process.
Run the `sudo` command printed as last output to configure the `pm2` process for automatic start on the system.
Run the `sudo` command printed as last output to configure the `pm2` process for automatic start on the system, as the current user.
## Development
@ -41,25 +43,19 @@ Run the `sudo` command printed as last output to configure the `pm2` process for
Before triggering the environment execution run
```text
npm install
```
inside the main folder, to process all needed npm package installations for the program execution.
To compile the Angular code and afterwards start the Express server with `nodemon` for development purpose run
```text
npm run dev
```
Any changes on `server` code will trigger an automatic restart of the Express server.
Any changes on `api` code will trigger an automatic restart of the Express server.
Changes on `static` code can be submitted with
```text
npm run deploy-static
```
The page must be reloaded after this build step is finished, in order to make changes visible.
@ -69,10 +65,10 @@ _TODO_
## License Information
### NodeJS Express Server (`/server`)
### Express API (`/api`)
published under [CC BY-SA 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt) \
Main concept for API Server, pagination and MongoDB usage by [Prof. Dr.-Ing. Johannes Konert](https://prof.beuth-hochschule.de/konert/) \
All endpoints, signature image builder and Arma3 RPT-Log parsing by [Florian Hartwich](https://de.linkedin.com/in/florian-hartwich-b67b02125)
### Angular 6 Frontend (`/static`)
### Angular 5 Frontend (`/static`)
published under [MIT License](https://opensource.org/licenses/MIT)

View File

@ -15,8 +15,7 @@ module.exports = {
request: rootRoute + '/request',
signatures: '/signatures',
signUp: rootRoute + '/authenticate/signup',
slotting: rootRoute + '/slotting',
squads: rootRoute + '/squads',
users: rootRoute + '/users',
wars: rootRoute + '/wars',
wars: rootRoute + '/wars'
};

23
api/config/config.js Normal file
View File

@ -0,0 +1,23 @@
module.exports = {
port: 8091,
database: {
uri: 'mongodb://localhost:27017/',
db: 'cc',
},
prod: {
env: 'production'
},
dev: {
env: 'dev'
},
test: {
port: 3001,
db: 'cc-test',
env: 'test'
}
};

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const cron = require('cron');
const async = require('async');
@ -19,8 +19,8 @@ const createAllSignatures = () => {
// mock response
const res = {
locals: {
items: {},
},
items: {}
}
};
// re-create signature image for each active user
@ -29,21 +29,21 @@ const createAllSignatures = () => {
// mock next to execute callback
const next = (err) => {
if (!err || (err && err.message.startsWith('Fraction not defined'))) {
callback();
callback()
} else {
error('\x1b[41m%s\x1b[0m', new Date().toLocaleString()
+ ': Error in execution - UPDATE SIGNATURES: ' + err.message);
+ ': Error in execution - UPDATE SIGNATURES: ' + err.message)
}
};
signatureTool(user._id, res, next);
signatureTool(user._id, res, next)
}, () => {
if (err) {
error('\x1b[41m%s\x1b[0m', new Date().toLocaleString()
+ ': Error in execution - UPDATE SIGNATURES: ' + err.message);
+ ': Error in execution - UPDATE SIGNATURES: ' + err.message)
}
logger('\x1b[35m%s\x1b[0m', new Date().toLocaleString()
+ ': finished successful - UPDATE SIGNATURES');
});
})
});
};
@ -57,7 +57,7 @@ const createBackup = () => {
logger('\x1b[32m%s\x1b[0m',stderr);
logger('\x1b[35m%s\x1b[0m', new Date().toLocaleString()
+ ': cron job finished - CREATE BACKUP');
});
})
};
// Execute daily @ 02:30 AM
@ -69,5 +69,5 @@ const cronJobBackup = cron.job('00 00 04 * * mon,thu,sat', createBackup);
module.exports = {
cronJobSignature: cronJobSignature,
cronJobBackup: cronJobBackup,
createAllSignatures: createAllSignatures,
createAllSignatures: createAllSignatures
};

View File

@ -1,15 +1,17 @@
'use strict';
"use strict";
const jwt = require('jsonwebtoken');
const config = require('../config/config');
const AppUser = require('../models/app-user');
const apiAuthentication = (req, res, next) => {
// check header or url parameters or post parameters for token
const token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
const secret = process.env.NODE_ENV === config.prod.env ? process.env.JWS_SECRET : 'dev-secret';
// verifies secret and checks exp
@ -23,7 +25,7 @@ const apiAuthentication = (req, res, next) => {
if (err) {
return res.status(403).send({
success: false,
message: 'token is not associated to any actual user',
message: 'token is not associated to any actual user'
});
}
req.user = item;
@ -31,13 +33,16 @@ const apiAuthentication = (req, res, next) => {
});
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.',
message: 'No token provided.'
});
}
};

View File

@ -10,7 +10,7 @@
* @module restapi/error-response
* @type {Function}
*/
'use strict';
"use strict";
const logger = require('debug')('me2:error-response');
module.exports = (app) => {
@ -23,8 +23,8 @@ module.exports = (app) => {
res.json({
error: {
message: err.message,
error: err.stack,
},
error: err.stack
}
});
});
} else {
@ -35,8 +35,8 @@ module.exports = (app) => {
res.json({
error: {
message: err.message,
error: {},
},
error: {}
}
});
});
}

View File

@ -19,17 +19,16 @@
// 1.) require() function
// 2.) module.exports
// 3.) exports (which is module.exports)
'use strict';
"use strict";
const express = require('express');
const logger = require('debug')('middleware:filterware');
/**
* private helper function to filter Objects by given keys
*
* @param {Array} keys - the keys from GET parameter filter
* @param {Object} schema - containing the keys as attributes that are allowed
* @return {Object} either the filtered items or an Error object
* @param keys {Array} the keys from GET parameter filter
* @param schema [Object} containing the keys as attributes that are allowed
* @returns {Object or Error} either the filtered items or an Error object
*/
const limitFilterToSchema = (keys, schema) => {
if (!keys || !schema) { // empty arrays evaluate to false
@ -46,7 +45,7 @@ const limitFilterToSchema = (keys, schema) => {
error = new Error('given key for filter does not exist in ressource: ' + key);
}
});
return error ? error : result;
return error ? error : result
};
/**
@ -58,7 +57,7 @@ const limitFilterToSchema = (keys, schema) => {
*/
const createFilterRouter = (schema, supressID) => {
const router = new express.Router();
const router = express.Router();
// the exported router with handler
router.use((req, res, next) => {
const filterString = req.query.filter;
@ -91,12 +90,12 @@ const createFilterRouter = (schema, supressID) => {
}
if (err) {
logger(err);
next(err);
next(err)
} else {
if (res.locals.filter) {
logger('Successfully set filter to ' + res.locals.filter);
}
next();
next()
}
});
return router;

View File

@ -17,10 +17,9 @@
// 1.) require() function
// 2.) module.exports
// 3.) exports (which is module.exports)
'use strict';
"use strict";
const express = require('express');
const router = new express.Router();
const router = require('express').Router();
const logger = require('debug')('middleware:offsetlimit');
@ -37,9 +36,10 @@ router.use((req, res, next) => {
if (!isNaN(offsetString)) {
offset = parseInt(offsetString);
if (offset < 0) {
err = new Error('offset is negative');
err = new Error('offset is negative')
}
} else {
}
else {
err = new Error('given offset is not a valid number ' + offsetString);
}
}
@ -47,21 +47,22 @@ router.use((req, res, next) => {
if (!isNaN(limitString)) {
limit = parseInt(limitString);
if (limit < 1) {
err = new Error('limit is zero or negative');
err = new Error('limit is zero or negative')
}
} else {
}
else {
err = new Error('given limit is not a valid number ' + limitString);
}
}
if (err) {
logger('problem occurred with limit/offset values');
err.status = 400;
next(err);
next(err)
} else {
res.locals.limitskip = {}; // mongoDB uses parameter object for skip/limit
if (limit) res.locals.limitskip.limit = limit;
if (offset) res.locals.limitskip.skip = offset;
next();
next()
}
});

View File

@ -0,0 +1,19 @@
"use strict";
let check = (requiredPermission, actualPermission, res, next) => {
if (actualPermission >= requiredPermission) {
return next();
}
return res.status(403).send({
success: false,
message: 'permission denied'
});
};
module.exports = {
checkSql: (req, res, next) => { check(1, 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

@ -15,10 +15,9 @@
// 1.) require() function
// 2.) module.exports
// 3.) exports (which is module.exports)
'use strict';
"use strict";
const express = require('express');
const router = new express.Router();
const router = require('express').Router();
// API-Version control. We use HTTP Header field Accept-Version instead of URL-part /v1/
router.use((req, res, next) => {
@ -43,7 +42,8 @@ router.use((req, res, next) => {
// send 406 that response will be application/json and request does not support it by now as answer
// user has REQUESTED the wrong type
res.status(406).send('response of application/json only supported, please accept this');
} else {
}
else {
next(); // let this request pass through as it is OK
}
});
@ -53,11 +53,11 @@ router.use((req, res, next) => {
router.use((req, res, next) => {
let err = undefined;
if (['POST', 'PUT', 'PATCH'].indexOf(req.method) > -1 && parseInt(req.get('Content-Length')) === 0) {
err = new Error('content in body is missing');
err = new Error("content in body is missing");
err.status = 400;
next(err);
} else if ('PUT' === req.method && !(req.body.id || req.body._id)) {
err = new Error('content in body is missing field id');
err = new Error("content in body is missing field id");
err.status = 400;
next(err);
}

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
// HTTP status codes by name
const codes = require('../routes/http-codes');

View File

@ -0,0 +1,20 @@
"use strict";
// HTTP status codes by name
const codes = require('../routes/http-codes');
/**
* check if id has valid UUID format
*/
const idValidator = (req, res, next) => {
const reqId = req.params.id;
if (!reqId.match(/^[0-9a-fA-F]{24}$/)) {
const err = new Error("Invalid request id format");
err.status = codes.notfound;
return next(err);
}
next();
};
exports.idValidator = idValidator;

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,36 +7,36 @@ const AppUserSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
unique: true
},
password: {
type: String,
required: true,
required: true
},
squad: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Squad',
default: null,
default: null
},
permission: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
get: v => Math.round(v),
set: v => Math.round(v),
min: 0,
max: 4,
default: 0,
default: 0
},
secret: {
type: String,
required: true,
required: true
},
activated: {
type: Boolean,
default: false,
},
default: false
}
}, {
collection: 'app_user',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
AppUserSchema.index({timestamp: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -6,39 +6,36 @@ const Schema = mongoose.Schema;
const AwardingSchema = new Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
ref: 'User'
},
decorationId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Decoration',
ref: 'Decoration'
},
reason: {
type: String,
required: true,
required: true
},
proposer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'AppUser',
required: true,
required: true
},
confirmed: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
get: v => Math.round(v),
set: v => Math.round(v),
min: 0,
max: 2,
default: 0,
},
rejectReason: {
type: String,
default: 0
},
date: {
type: Date,
default: Date.now(),
},
default: Date.now()
}
}, {
collection: 'awarding',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
AwardingSchema.index({timestamp: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -6,11 +6,11 @@ const Schema = mongoose.Schema;
const CampaignSchema = new Schema({
title: {
type: String,
required: true,
},
required: true
}
}, {
collection: 'campaign',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
CampaignSchema.index({timestamp: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -6,27 +6,27 @@ const Schema = mongoose.Schema;
const DecorationSchema = new Schema({
name: {
type: String,
required: true,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR', 'ARF', 'SWORD', 'GLOBAL'],
required: true,
enum: ['BLUFOR', 'OPFOR', 'GLOBAL'],
required: true
},
description: {
type: String,
required: true,
required: true
},
sortingNumber: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
default: 0,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
},
isMedal: {type: Boolean, required: true},
isMedal: {type: Boolean, required: true}
}, {
collection: 'decoration',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
DecorationSchema.index({timestamp: 1});

39
api/models/logs/budget.js Normal file
View File

@ -0,0 +1,39 @@
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const LogBudgetSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true
},
time: {
type: Date,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR'],
required: true
},
oldBudget: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
newBudget: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
}
}, {
collection: 'logBudget'
});
// optional more indices
LogBudgetSchema.index({war: 1});
module.exports = mongoose.model('LogBudget', LogBudgetSchema);

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,28 +7,27 @@ const LogFlagSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true,
required: true
},
time: {
type: Date,
required: true,
required: true
},
player: {
type: String,
required: true,
required: true
},
flagFraction: {
type: String,
enum: ['BLUFOR', 'OPFOR'],
required: true,
required: true
},
capture: {
type: Boolean,
required: true,
},
required: true
}
}, {
collection: 'logFlag',
versionKey: false,
collection: 'logFlag'
});
// optional more indices
LogFlagSchema.index({war: 1, player: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,40 +7,30 @@ const LogKillSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true,
required: true
},
time: {
type: Date,
required: true,
required: true
},
shooter: {
type: String,
type: String
},
target: {
type: String,
required: true,
required: true
},
friendlyFire: {
type: Boolean,
required: true,
},
magazine: {
type: String,
},
shooterVehicle: {
type: String,
},
targetVehicle: {
type: String,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR', 'NONE'],
required: true,
},
required: true
}
}, {
collection: 'logKill',
versionKey: false,
collection: 'logKill'
});
// optional more indices
LogKillSchema.index({war: 1, shooter: 1, target: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,32 +7,31 @@ const LogKillSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true,
required: true
},
time: {
type: Date,
required: true,
required: true
},
ptBlufor: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
required: true,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
ptOpfor: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
required: true,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR', 'NONE'],
required: true,
},
required: true
}
}, {
collection: 'logPoints',
versionKey: false,
collection: 'logPoints'
});
// optional more indices
LogKillSchema.index({war: 1, shooter: 1, target: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,19 +7,18 @@ const LogRespawnSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true,
required: true
},
time: {
type: Date,
required: true,
required: true
},
player: {
type: String,
required: true,
},
required: true
}
}, {
collection: 'logRespawn',
versionKey: false,
collection: 'logRespawn'
});
// optional more indices
LogRespawnSchema.index({war: 1, player: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,32 +7,31 @@ const LogReviveSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true,
required: true
},
time: {
type: Date,
required: true,
required: true
},
medic: {
type: String,
required: true,
required: true
},
patient: {
type: String,
required: true,
required: true
},
stabilized: {
type: Boolean,
required: true,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR'],
required: true,
},
required: true
}
}, {
collection: 'logRevive',
versionKey: false,
collection: 'logRevive'
});
// optional more indices
LogReviveSchema.index({war: 1, medic: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,36 +7,35 @@ const LogTransportSchema = new Schema({
war: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true,
required: true
},
time: {
type: Date,
required: true,
required: true
},
driver: {
type: String,
required: true,
required: true
},
passenger: {
type: String,
required: true,
required: true
},
distance: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
required: true,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR'],
required: true,
},
required: true
}
}, {
collection: 'logTransport',
versionKey: false,
collection: 'logTransport'
});
// optional more indices
LogTransportSchema.index({war: 1, driver: 1});
LogTransportSchema.index({war: 1});
module.exports = mongoose.model('LogTransport', LogTransportSchema);

69
api/models/player.js Normal file
View File

@ -0,0 +1,69 @@
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PlayerSchema = new Schema({
name: {
type: String,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR'],
required: true
},
warId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'War',
required: true
},
kill: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
death: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
friendlyFire: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
revive: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
respawn: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
flagTouch: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
sort: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v)
}
}, {
collection: 'player',
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
PlayerSchema.index({timestamp: 1});
module.exports = mongoose.model('Player', PlayerSchema);

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -6,39 +6,36 @@ const Schema = mongoose.Schema;
const PromotionSchema = new Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
ref: 'User'
},
proposer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'AppUser',
required: true,
required: true
},
oldRankLvl: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
required: true,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
newRankLvl: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
required: true,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
confirmed: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
get: v => Math.round(v),
set: v => Math.round(v),
min: 0,
max: 2,
required: true,
},
rejectReason: {
type: String,
},
required: true
}
}, {
collection: 'promotion',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
PromotionSchema.index({timestamp: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -6,22 +6,22 @@ const Schema = mongoose.Schema;
const RankSchema = new Schema({
name: {
type: String,
required: true,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR'],
required: true,
required: true
},
level: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
required: true,
},
get: v => Math.round(v),
set: v => Math.round(v),
required: true
}
}, {
collection: 'rank',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
RankSchema.index({timestamp: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -6,22 +6,22 @@ const Schema = mongoose.Schema;
const SquadSchema = new Schema({
name: {
type: String,
required: true,
required: true
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR'],
required: true,
required: true
},
sortingNumber: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
default: 0,
},
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
}
}, {
collection: 'squad',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
SquadSchema.index({timestamp: 1});

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
@ -7,22 +7,22 @@ const UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
unique: true
},
rankLvl: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
default: 0,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
},
squadId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Squad',
default: null,
},
default: null
}
}, {
collection: 'user',
timestamps: {createdAt: 'timestamp'},
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
UserSchema.index({timestamp: 1});

75
api/models/war.js Normal file
View File

@ -0,0 +1,75 @@
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const WarSchema = new Schema({
title: {
type: String,
required: true
},
date: {
type: Date,
},
endDate : {
type: Date,
},
ptBlufor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
},
ptOpfor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
},
playersBlufor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
},
playersOpfor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
},
campaign: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Campaign',
required: true
},
budgetBlufor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
},
budgetOpfor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
},
endBudgetBlufor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
},
endBudgetOpfor: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
default: 0
}
}, {
collection: 'war',
timestamps: {createdAt: 'timestamp'}
});
// optional more indices
WarSchema.index({timestamp: 1});
module.exports = mongoose.model('War', WarSchema);

6230
api/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
api/package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "opt-cc-api",
"licence": "CC BY-SA 4.0",
"description": "RESTful API for Operation Pandora Trigger Command Center, includes signature generator",
"main": "server.js",
"author": "Florian Hartwich <hardi@noarch.de>",
"private": true,
"scripts": {
"start": "DEBUG='cc:*' NODE_ENV=production node server.js",
"dev": "DEBUG='cc:*' NODE_ENV=dev nodemon server.js",
"test": "mocha --require ./test/config/spec_helper.js",
"e2e": "NODE_ENV=test node server.js"
},
"dependencies": {
"async": "^2.5.0",
"bcryptjs": "^2.4.3",
"body-parser": "~1.13.2",
"cors": "^2.8.4",
"cron": "^1.3.0",
"debug": "^3.1.0",
"express": "^4.16.2",
"imagemin": "^5.2.2",
"imagemin-pngquant": "^5.0.0",
"jimp": "^0.2.27",
"jsonwebtoken": "^7.4.3",
"lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"mongoose": "^5.0.3",
"morgan": "~1.6.1",
"multer": "^1.3.0",
"node-sha1": "^1.0.1",
"q": "^1.5.0",
"serve-favicon": "~2.3.0",
"supports-color": "^5.1.0"
},
"devDependencies": {
"chai": "^3.5.0",
"chai-http": "^3.0.0",
"mocha": "^3.5.3",
"nodemon": "^1.14.12"
}
}

87
api/routes/account.js Normal file
View File

@ -0,0 +1,87 @@
"use strict";
// modules
const express = require('express');
const logger = require('debug')('cc:awardings');
// HTTP status codes by name
const codes = require('./http-codes');
const routerHandling = require('../middleware/router-handling');
// Mongoose Model using mongoDB
const AppUserModel = require('../models/app-user');
const account = express.Router();
account.route('/')
.get((req, res, next) => {
AppUserModel.find({}, {}, {sort: {username: 1}}).populate('squad').exec((err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
res.locals.items = items;
res.locals.processed = true;
next();
})
})
.all(
routerHandling.httpMethodNotAllowed
);
// routes **********************
account.route('/:id')
.patch((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};
// 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.
AppUserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}).populate('squad').exec((err, item) => {
if (err) {
err.status = codes.wrongrequest;
}
else if (!item) {
err = new Error("appUser not found");
err.status = codes.notfound;
}
else {
res.locals.items = item;
}
next(err);
})
})
.delete((req, res, next) => {
AppUserModel.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;
}
// 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
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
account.use(routerHandling.emptyResponse);
module.exports = account;

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
// modules
const express = require('express');
@ -6,6 +6,7 @@ const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const Q = require('q');
const _ = require('lodash');
const logger = require('debug')('cc:authenticate');
// HTTP status codes by name
const codes = require('./http-codes');
@ -16,7 +17,7 @@ const routerHandling = require('../middleware/router-handling');
const AppUserModel = require('../models/app-user');
const authenticate = new express.Router();
const authenticate = express.Router();
// routes **********************
authenticate.route('/')
@ -46,7 +47,7 @@ let authCheck = (username, password, res) => {
AppUserModel.findOne({username: username.toLowerCase()}).populate('squad').exec((err, user) => {
if (err) deferred.reject(err.name + ': ' + err.message);
const diff = 28 * 60 * 24; // time till expiration [minutes]
const diff = 7 * 60 * 24; // time till expiration [minutes]
if (user && !user.activated) {
res.status(codes.unauthorized).send('Account is not yet activated');
@ -61,7 +62,7 @@ let authCheck = (username, password, res) => {
permission: user.permission,
squad: user.squad,
token: jwt.sign({sub: user._id}, secret, {expiresIn: diff * 60}),
tokenExpireDate: new Date(Date.now().valueOf() + diff * 60000 - 1000),
tokenExpireDate: new Date(Date.now().valueOf() + diff * 60000 - 1000)
});
} else {
// authentication failed
@ -78,10 +79,10 @@ authenticate.route('/signup')
.post((req, res, next) => {
create(req.body)
.then(() => {
res.status(codes.created).send({message: 'User successfully created'});
res.sendStatus(200);
})
.catch((err) => {
res.status(codes.wrongrequest).send(err);
res.status(400).send(err);
});
})
@ -100,7 +101,7 @@ let create = (userParam) => {
if (user) {
// username already exists
deferred.reject(new Error('Username already exists'));
deferred.reject(new Error("Username already exists"));
} else {
createUser();
}

161
api/routes/awardings.js Normal file
View File

@ -0,0 +1,161 @@
"use strict";
// modules
const express = require('express');
const logger = require('debug')('cc:awardings');
// HTTP status codes by name
const codes = require('./http-codes');
const routerHandling = require('../middleware/router-handling');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkHl = require('../middleware/permission-check').checkHl;
// Mongoose Model using mongoDB
const AwardingModel = require('../models/awarding');
// result set for proposer(appUser) population
const resultSet = {
'__v': 0,
'updatedAt': 0,
'timestamp': 0,
'password': 0,
'permission': 0,
'secret': 0,
'activated': 0
};
const awarding = express.Router();
// routes **********************
awarding.route('/')
.get((req, res, next) => {
const filter = {};
if (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;
} else {
res.locals.items = items;
}
res.locals.processed = true;
next();
});
}
})
.post(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
const award = new AwardingModel(req.body);
award.confirmed = 1;
award.proposer = req.user._id;
// timestamp and default are set automatically by Mongoose Schema Validation
award.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 = award;
next();
});
})
.all(
routerHandling.httpMethodNotAllowed
);
awarding.route('/:id')
.patch(apiAuthenticationMiddleware, checkHl, (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.
}
// optional task 3: increment version manually as we do not use .save(.)
req.body.updatedAt = new Date();
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.
AwardingModel.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) => {
AwardingModel.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;
}
// 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
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
awarding.use(routerHandling.emptyResponse);
module.exports = awarding;

88
api/routes/campaigns.js Normal file
View File

@ -0,0 +1,88 @@
"use strict";
// modules
const express = require('express');
const logger = require('debug')('cc:campaigns');
// HTTP status codes by name
const codes = require('./http-codes');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkMT = require('../middleware/permission-check').checkMT;
const routerHandling = require('../middleware/router-handling');
const idValidator = require('../middleware/validators').idValidator;
// Mongoose Model using mongoDB
const CampaignModel = require('../models/campaign');
const WarModel = require('../models/war');
const campaigns = 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
campaign.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 = campaign;
next();
});
})
.all(
routerHandling.httpMethodNotAllowed
);
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();
});
})
.delete((req, res, next) => {
CampaignModel.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);
}
WarModel.find({campaign: req.params.id}).remove().exec();
res.locals.processed = true;
next();
})
})
.all(
routerHandling.httpMethodNotAllowed
);
// 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
campaigns.use(routerHandling.emptyResponse);
module.exports = campaigns;

41
api/routes/command.js Normal file
View File

@ -0,0 +1,41 @@
"use strict";
// modules
const express = require('express');
const logger = require('debug')('cc:command');
// HTTP status codes by name
const codes = require('./http-codes');
const routerHandling = require('../middleware/router-handling');
const createAllSignatures = require('../cron-job/cron').createAllSignatures;
const createSignature = require('../tools/signature-tool');
const command = express.Router();
command.route('/createSignature')
.post((req, res, next) => {
createAllSignatures();
})
.all(
routerHandling.httpMethodNotAllowed
);
command.route('/createSignature/:id')
.post((req, res, next) => {
const userId = req.params.id;
createSignature(userId, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
// 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
command.use(routerHandling.emptyResponse);
module.exports = command;

165
api/routes/decorations.js Normal file
View File

@ -0,0 +1,165 @@
"use strict";
// modules
const fs = require('fs');
const express = require('express');
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({storage: storage});
const logger = require('debug')('cc:decorations');
// HTTP status codes by name
const codes = require('./http-codes');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkHl = require('../middleware/permission-check').checkHl;
const routerHandling = require('../middleware/router-handling');
const idValidator = require('../middleware/validators').idValidator;
// Mongoose Model using mongoDB
const DecorationModel = require('../models/decoration');
const AwardingsModel = require('../models/awarding');
const decoration = express.Router();
// routes **********************
decoration.route('/')
.get((req, res, next) => {
const filter = {};
if (req.query.fractFilter) {
filter.fraction = req.query.fractFilter.toUpperCase()
}
if (req.query.q) {
filter.name = {$regex: req.query.q, $options: 'i'}
}
DecorationModel.find(filter, {}, {sort: {fraction: 'asc', isMedal: 'asc', sortingNumber: 'asc', name: 'asc'}}, (err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
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) => {
const decoration = new DecorationModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation
decoration.save((err) => {
if (err) {
err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
return next(err);
}
res.status(codes.created);
res.locals.items = decoration;
fs.appendFile(__dirname + '/../resource/decoration/' + decoration.id + '.png', new Buffer(req.file.buffer),
(err) => {
if (err) next(err);
});
next();
})
})
.all(
routerHandling.httpMethodNotAllowed
);
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();
});
})
.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.
}
// optional task 3: 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/decoration/' + 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.
DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) {
err.status = codes.wrongrequest;
}
else if (!item) {
err = new Error("item not found");
err.status = codes.notfound;
}
else {
res.locals.items = item;
}
next(err);
})
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
DecorationModel.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;
}
// deleted all awardings linked to this decoration
AwardingsModel.find({decorationId: req.params.id}).remove().exec();
// delete graphic
fs.unlink(__dirname + '/../resource/decoration/' + 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
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
decoration.use(routerHandling.emptyResponse);
module.exports = decoration;

View File

@ -4,7 +4,7 @@
* @author Johannes Konert
* @module http-codes
*/
'use strict';
"use strict";
module.exports = {
success: 200,
created: 201,
@ -17,5 +17,5 @@ module.exports = {
conflict: 409,
wrongdatatyperequest: 406,
wrongmediasend: 415,
servererror: 500,
servererror: 500
};

156
api/routes/logs.js Normal file
View File

@ -0,0 +1,156 @@
"use strict";
// modules
const express = require('express');
const async = require('async');
const logger = require('debug')('cc:logs');
const routerHandling = require('../middleware/router-handling');
const decimalToTimeString = require('../tools/util').decimalToTimeString;
// Mongoose Model using mongoDB
const LogBudgetModel = require('../models/logs/budget');
const LogRespawnModel = require('../models/logs/respawn');
const LogReviveModel = require('../models/logs/revive');
const LogKillModel = require('../models/logs/kill');
const LogTransportModel = require('../models/logs/transport');
const LogFlagModel = require('../models/logs/flag');
const LogPointsModel = require('../models/logs/points');
const logsRouter = express.Router();
function processLogRequest(model, filter, res, next) {
model.find(filter, {}, {sort: {time: 1}}, (err, log) => {
if (err) return next(err);
if (!log || log.length === 0) {
const err = new Error('No logs found');
err.status = require('./http-codes').notfound;
return next(err)
}
res.locals.items = log;
next();
})
}
// routes **********************
logsRouter.route('/:warId')
.get((req, res, next) => {
const filter = {war: req.params.warId};
const sort = {sort: {time: 1}};
const pointsObjects = LogPointsModel.find(filter, {}, sort);
const budgetObjects = LogBudgetModel.find(filter, {}, sort);
const respawnObjects = LogRespawnModel.find(filter, {}, sort);
const reviveObjects = LogReviveModel.find(filter, {}, sort);
const killObjects = LogKillModel.find(filter, {}, sort);
const transportObjects = LogTransportModel.find(filter, {}, sort);
const flagObjects = LogFlagModel.find(filter, {}, sort);
const resources = {
points: pointsObjects.exec.bind(pointsObjects),
budget: budgetObjects.exec.bind(budgetObjects),
respawn: respawnObjects.exec.bind(respawnObjects),
revive: reviveObjects.exec.bind(reviveObjects),
kill: killObjects.exec.bind(killObjects),
transport: transportObjects.exec.bind(transportObjects),
flag: flagObjects.exec.bind(flagObjects)
};
async.parallel(resources, function (error, results){
if (error) {
res.status(500).send(error);
return;
}
res.locals.items = results;
next();
});
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.route('/:warId/budget')
.get((req, res, next) => {
const filter = {war: req.params.warId};
if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogBudgetModel, filter, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.route('/:warId/respawn')
.get((req, res, next) => {
const filter = {war: req.params.warId};
if (req.query.player) filter['player'] = req.query.player;
processLogRequest(LogRespawnModel, filter, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.route('/:warId/revive')
.get((req, res, next) => {
const filter = {war: req.params.warId};
if (req.query.medic) filter['medic'] = req.query.medic;
if (req.query.patient) filter['patient'] = req.query.patient;
if (req.query.stabilized) filter['stabilized'] = true;
if (req.query.revive) filter['stabilized'] = false;
if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogReviveModel, filter, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.route('/:warId/kills')
.get((req, res, next) => {
const filter = {war: req.params.warId};
if (req.query.shooter) filter['shooter'] = req.query.shooter;
if (req.query.target) filter['target'] = req.query.target;
if (req.query.friendlyFire) filter['friendlyFire'] = true;
if (req.query.noFriendlyFire) filter['friendlyFire'] = false;
if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogKillModel, filter, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.route('/:warId/transport')
.get((req, res, next) => {
const filter = {war: req.params.warId};
if (req.query.driver) filter['driver'] = req.query.driver;
if (req.query.passenger) filter['passenger'] = req.query.passenger;
if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogTransportModel, filter, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.route('/:warId/flag')
.get((req, res, next) => {
const filter = {war: req.params.warId};
if (req.query.player) filter['player'] = req.query.player;
if (req.query.capture) filter['capture'] = true;
if (req.query.defend) filter['capture'] = false;
if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogFlagModel, filter, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.route('/:warId/points')
.get((req, res, next) => {
const filter = {war: req.params.warId};
if (req.query.fraction) filter['fraction'] = req.query.fraction;
processLogRequest(LogPointsModel, filter, res, next);
})
.all(
routerHandling.httpMethodNotAllowed
);
logsRouter.use(routerHandling.emptyResponse);
module.exports = logsRouter;

113
api/routes/overview.js Normal file
View File

@ -0,0 +1,113 @@
"use strict";
// modules
const async = require('async');
const express = require('express');
const logger = require('debug')('cc:overview');
// HTTP status codes by name
const codes = require('./http-codes');
const routerHandling = require('../middleware/router-handling');
// Mongoose Model using mongoDB
const UserModel = require('../models/user');
const SquadModel = require('../models/squad');
const RankModel = require('../models/rank');
const overview = express.Router();
// routes **********************
overview.route('/')
.get((req, res, next) => {
let countOpfor = 0;
let countBlufor = 0;
const armyOverview = {
BLUFOR: {
squads: []
},
OPFOR: {
squads: []
}
};
SquadModel.find({}, {'sortingNumber': 0, 'updatedAt': 0, 'timestamp': 0, '__v': 0}, {
sort: {
sortingNumber: 'asc',
name: 'asc'
}
}, (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) {
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();
});
});
})
.all(
routerHandling.httpMethodNotAllowed
);
overview.use(routerHandling.emptyResponse);
module.exports = overview;

114
api/routes/players.js Normal file
View File

@ -0,0 +1,114 @@
"use strict";
// modules
const express = require('express');
const logger = require('debug')('cc:players');
// HTTP status codes by name
const codes = require('./http-codes');
const routerHandling = require('../middleware/router-handling');
// Mongoose Model using mongoDB
const CampaignModel = require('../models/campaign');
const PlayerModel = require('../models/player');
const WarModel = require('../models/war');
const campaignPlayer = express.Router();
// routes **********************
campaignPlayer.route('/ranking/:campaignId')
.get((req, res, next) => {
WarModel.find({campaign: req.params.campaignId}, '_id', (err, wars) => {
if (err) return next(err);
const warIds = wars.map((obj) => {
return obj._id;
});
PlayerModel.find({warId: {"$in": warIds}}, (err, items) => {
if (err) return next(err);
if (!items || items.length === 0) {
const err = new Error('No players for given campaignId');
err.status = codes.notfound;
return next(err)
}
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) {
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;
}
res.locals.items = {
kill: getSortedField('kill'),
death: getSortedField('death'),
friendlyFire: getSortedField('friendlyFire'),
revive: getSortedField('revive'),
respawn: getSortedField('respawn'),
flagTouch: getSortedField('flagTouch')
};
next();
})
})
})
.all(
routerHandling.httpMethodNotAllowed
);
campaignPlayer.route('/single/:campaignId/:playerName')
.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;
});
PlayerModel.find({name: req.params.playerName, warId: {"$in": warIds}})
.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(
routerHandling.httpMethodNotAllowed
);
campaignPlayer.use(routerHandling.emptyResponse);
module.exports = campaignPlayer;

158
api/routes/ranks.js Normal file
View File

@ -0,0 +1,158 @@
"use strict";
// modules
const fs = require('fs');
const express = require('express');
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({storage: storage});
const logger = require('debug')('cc:ranks');
// HTTP status codes by name
const codes = require('./http-codes');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkHl = require('../middleware/permission-check').checkHl;
const routerHandling = require('../middleware/router-handling');
const idValidator = require('../middleware/validators').idValidator;
// Mongoose Model using mongoDB
const RankModel = require('../models/rank');
const ranks = express.Router();
// routes **********************
ranks.route('/')
.get((req, res, next) => {
const filter = {};
if (req.query.fractFilter) {
filter.fraction = req.query.fractFilter.toUpperCase()
}
if (req.query.q) {
filter.name = {$regex: req.query.q, $options: 'i'}
}
RankModel.find(filter, {}, {sort: {fraction: 'asc', level: 'asc'}}, (err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
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) => {
const rank = new RankModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation
rank.save((err) => {
if (err) {
err.status = codes.wrongrequest;
next(err);
}
res.status(codes.created);
res.locals.items = rank;
fs.appendFile(__dirname + '/../resource/rank/' + rank.id + '.png', new Buffer(req.file.buffer),
(err) => {
if (err) next(err);
});
next();
});
})
.all(
routerHandling.httpMethodNotAllowed
);
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();
});
})
.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.
}
// optional task 3: 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/rank/' + 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.
RankModel.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) => {
RankModel.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;
}
// 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
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
ranks.use(routerHandling.emptyResponse);
module.exports = ranks;

183
api/routes/request.js Normal file
View File

@ -0,0 +1,183 @@
"use strict";
// modules
const express = require('express');
const logger = require('debug')('cc:awardings');
// HTTP status codes by name
const codes = require('./http-codes');
const routerHandling = require('../middleware/router-handling');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkSql = require('../middleware/permission-check').checkSql;
const checkHl = require('../middleware/permission-check').checkHl;
// Mongoose Model using mongoDB
const UserModel = require('../models/user');
const AwardingModel = require('../models/awarding');
const PromotionModel = require('../models/promotion');
// result set for proposer(appUser) population
const resultSet = {
'__v': 0,
'updatedAt': 0,
'timestamp': 0,
'password': 0,
'permission': 0,
'secret': 0,
'activated': 0
};
const request = express.Router();
// routes **********************
request.route('/award')
.post(apiAuthenticationMiddleware, checkSql, (req, res, next) => {
const award = new AwardingModel(req.body);
award.confirmed = 0;
award.proposer = req.user._id;
// timestamp and default are set automatically by Mongoose Schema Validation
award.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 = award;
next();
});
})
.all(
routerHandling.httpMethodNotAllowed
);
request.route('/promotion')
.get((req, res, next) => {
// TODO: add SQL authentication
const squadFilter = req.query.squadId;
const fractFilter = req.query.fractFilter;
const progressFilter = req.query.inProgress;
let filter;
if (squadFilter) {
filter = {squadId: squadFilter};
}
let userIds = [];
UserModel.find(filter).populate('squadId').exec((err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
for (let item of items) {
if (!fractFilter || (fractFilter && item.squadId && item.squadId.fraction === fractFilter)) {
userIds.push(item._id);
}
}
let promotionFilter = {
userId: {"$in": userIds}
};
if (progressFilter) {
promotionFilter.confirmed = 0;
}
PromotionModel.find(promotionFilter, {}, {sort: {timestamp: 'desc'}})
.populate('userId').populate('proposer', resultSet).exec((err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
if (items && items.length > 0) {
res.locals.items = items;
} else {
res.locals.items = [];
}
res.locals.processed = true;
next();
})
});
})
.post(apiAuthenticationMiddleware, checkSql, (req, res, next) => {
const promotion = new PromotionModel(req.body);
promotion.confirmed = 0;
promotion.proposer = req.user._id;
// timestamp and default are set automatically by Mongoose Schema Validation
promotion.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 = promotion;
next();
});
})
.all(
routerHandling.httpMethodNotAllowed
);
request.route('/promotion/:id')
.patch(apiAuthenticationMiddleware, checkHl, (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.
}
req.body.updatedAt = new Date();
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.
PromotionModel.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 {
if (item.confirmed === 1) {
let updateUser = {
_id: item.userId,
rankLvl: item.newRankLvl
};
UserModel.findByIdAndUpdate(updateUser._id, updateUser, {new: true}, (err, item) => {
if (err) {
err.status = codes.wrongrequest;
}
else if (!item) {
err = new Error("user not found");
err.status = codes.notfound;
}
});
}
res.locals.items = item;
}
next(err);
})
})
.all(
routerHandling.httpMethodNotAllowed
);
// 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
request.use(routerHandling.emptyResponse);
module.exports = request;

58
api/routes/signatures.js Normal file
View File

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

164
api/routes/squads.js Normal file
View File

@ -0,0 +1,164 @@
"use strict";
// modules
const fs = require('fs');
const express = require('express');
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({storage: storage});
const logger = require('debug')('cc:squads');
// HTTP status codes by name
const codes = require('./http-codes');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkHl = require('../middleware/permission-check').checkHl;
const routerHandling = require('../middleware/router-handling');
const idValidator = require('../middleware/validators').idValidator;
// Mongoose Model using mongoDB
const SquadModel = require('../models/squad');
const squads = express.Router();
// routes **********************
squads.route('/')
.get((req, res, next) => {
const filter = {};
if (req.query.fractFilter) {
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);
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')
.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();
});
})
.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.
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
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
squads.use(routerHandling.emptyResponse);
module.exports = squads;

243
api/routes/users.js Normal file
View File

@ -0,0 +1,243 @@
"use strict";
// modules
const express = require('express');
const fs = require('fs');
const logger = require('debug')('me2u5:users');
// HTTP status codes by name
const codes = require('./http-codes');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkHl = require('../middleware/permission-check').checkHl;
const offsetlimitMiddleware = require('../middleware/limitoffset-middleware-mongo');
const filterHandlerCreator = require('../middleware/filter-handler-mongo');
const routerHandling = require('../middleware/router-handling');
const idValidator = require('../middleware/validators').idValidator;
// Mongoose Model using mongoDB
const UserModel = require('../models/user');
const SquadModel = require('../models/squad');
const AwardingModel = require('../models/awarding');
const users = express.Router();
users.get('/', filterHandlerCreator(UserModel.schema.paths));
users.get('/', offsetlimitMiddleware);
// routes **********************
users.route('/')
.get((req, res, next) => {
const userQuery = () => {
UserModel.find(dbFilter, res.locals.filter, res.locals.limitskip)
.populate('squadId')
.collation({locale: "en", strength: 2}) // case insensitive order
.sort('username').exec((err, users) => {
if (err) return next(err);
if (users.length === 0) {
res.locals.items = users;
res.locals.processed = true;
return next();
}
UserModel.count(dbFilter, (err, totalCount) => {
res.set('x-total-count', totalCount);
res.locals.items = users;
res.locals.processed = true;
return next();
})
})
};
if (!req.query.q) req.query.q = '';
const dbFilter = {username: {"$regex": req.query.q, "$options": "i"}};
if (req.query.squadId) dbFilter["squadId"] = {"$eq": req.query.squadId};
// squad / fraction filter setup
if (req.query.fractFilter && req.query.fractFilter !== 'UNASSIGNED' && !req.query.squadId) {
SquadModel.find({'fraction': req.query.fractFilter}, {_id: 1}, (err, squads) => {
dbFilter['squadId'] = {$in: squads.map(squad => squad.id)};
userQuery();
})
} else {
if (req.query.fractFilter === 'UNASSIGNED') {
dbFilter['squadId'] = {$eq: null};
}
userQuery();
}
})
.post(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
const user = new UserModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation
user.save((err) => {
if (err) {
err.status = codes.wrongrequest;
return next(err);
}
res.status(codes.created);
UserModel.populate(user, {path: 'squadId'}, (err, extUser) => {
res.locals.items = extUser;
res.locals.processed = true;
return next();
})
});
})
.all(routerHandling.httpMethodNotAllowed);
users.route('/:id')
.get(idValidator, (req, res, next) => {
UserModel.findById(req.params.id).populate('squadId').exec((err, user) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
else if (!user) {
err = new Error("item not found");
err.status = codes.notfound;
return next(err);
}
res.locals.items = user;
res.locals.processed = true;
return next();
});
})
.patch(apiAuthenticationMiddleware, checkHl, (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.
}
// optional task 3: increment version manually as we do not use .save(.)
req.body.updatedAt = new Date();
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.
UserModel.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;
}
UserModel.populate(item, {path: 'squadId'}, (err, extUser) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
if (!user) {
res.locals.items = {};
res.locals.processed = true;
return next();
}
res.locals.items = extUser;
res.locals.processed = true;
return next();
})
})
})
.put(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
// first check that the given element id is the same as the URL id
if (!req.body || req.body._id !== req.params.id) {
// 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);
err.status = codes.notfound;
next(err);
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
const user = new UserModel(req.body);
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.
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);
}
// check that version is still accurate
else if (user.__v !== item.__v) {
err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again")
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) {
if ((field !== '_id') && (field !== '__v')) {
// this includes undefined. is important to reset attributes that are missing in req.body
item.set(field, user[field]);
}
}
// update updatedAt and increase version
item.updatedAt = new Date();
item.increment(); // this sets __v++
item.save(function (err) {
if (!err) {
res.locals.items = item;
} else {
err.status = codes.wrongrequest;
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
}
UserModel.populate(item, {path: 'squadId'}, (err, extUser) => {
res.locals.items = extUser;
res.locals.processed = true;
return next();
})
});
})
})
.delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => {
UserModel.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;
}
// deleted all awardings linked to this user
AwardingModel.find({userId: req.params.id}).remove().exec();
// check if signature exists and delete compressed and uncompressed file
const fileMinified = __dirname + '/../resource/signature/' + req.params.id + '.png';
if (fs.existsSync(fileMinified)) {
fs.unlink(fileMinified, (err) => {
});
}
const file = __dirname + '/../resource/signature/big/' + req.params.id + '.png';
if (fs.existsSync(file)) {
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(..)
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
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
users.use(routerHandling.emptyResponse);
module.exports = users;

225
api/routes/wars.js Normal file
View File

@ -0,0 +1,225 @@
"use strict";
// modules
const fs = require('fs');
const mkdirp = require("mkdirp");
const express = require('express');
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({storage: storage});
const logger = require('debug')('cc:wars');
// HTTP status codes by name
const codes = require('./http-codes');
// access check
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkMT = require('../middleware/permission-check').checkMT;
const routerHandling = require('../middleware/router-handling');
const idValidator = require('../middleware/validators').idValidator;
// log paser tool
const parseWarLog = require('../tools/log-parse-tool');
// Mongoose Model using mongoDB
const CampaignModel = require('../models/campaign');
const WarModel = require('../models/war');
const PlayerModel = require('../models/player');
const LogKillModel = require('../models/logs/kill');
const LogRespawnModel = require('../models/logs/respawn');
const LogReviveModel = require('../models/logs/revive');
const LogTransportModel = require('../models/logs/transport');
const LogFlagModel = require('../models/logs/flag');
const LogBudgetModel = require('../models/logs/budget');
const LogPointsModel = require('../models/logs/points');
const wars = express.Router();
// routes **********************
wars.route('/')
.get((req, res, next) => {
let result = [];
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) {
return next(err);
}
const lineArray = file.toString().split("\n");
const statsResult = parseWarLog(lineArray, warBody);
statsResult.war.save((err, war) => {
if (err) {
return next(err);
}
PlayerModel.create(statsResult.players, function (err) {
if (err) {
return next(err);
}
LogKillModel.create(statsResult.kills, function () {
LogRespawnModel.create(statsResult.respawn, function () {
LogReviveModel.create(statsResult.revive, function () {
LogFlagModel.create(statsResult.flag, function () {
LogBudgetModel.create(statsResult.budget, function () {
LogTransportModel.create(statsResult.transport, 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
const cleanFile = fs.createWriteStream(folderName + '/clean.log');
statsResult.clean.forEach(cleanLine => {
cleanFile.write(cleanLine + '\n\n')
});
cleanFile.end();
// save raw log file
const rawFile = fs.createWriteStream(folderName + '/war.log');
lineArray.forEach(rawLine => {
rawFile.write(rawLine + '\n')
});
rawFile.end();
res.status(codes.created);
res.locals.items = war;
next();
})
})
})
})
})
})
})
})
})
})
});
} else {
const err = new Error('no Logfile provided');
err.status = codes.wrongmediasend;
next(err);
}
})
.all(
routerHandling.httpMethodNotAllowed
);
wars.route('/:id')
.get(idValidator, (req, res, next) => {
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) {
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);
}
// 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';
if (fs.existsSync(sourceLog)) {
fs.unlink(sourceLog, (err) => {
});
}
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(
routerHandling.httpMethodNotAllowed
);
// 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
wars.use(routerHandling.emptyResponse);
module.exports = wars;

127
api/server.js Normal file
View File

@ -0,0 +1,127 @@
"use strict";
const express = require('express');
const path = require('path');
const favicon = require('serve-favicon');
const bodyParser = require('body-parser');
const requestLogger = require('morgan');
const debug = require('debug');
const error = debug('cc:server:err');
const logger = debug('cc:server');
logger.log = console.log.bind(console);
const cors = require('cors')
const mongoose = require('mongoose');
// own modules
const config = require('./config/config');
const urls = require('./config/api-url');
const restAPIchecks = require('./middleware/request-checks.js');
const errorResponseWare = require('./middleware/error-response');
const apiAuthenticationMiddleware = require('./middleware/auth-middleware');
const checkAdmin = require('./middleware/permission-check').checkAdmin;
const signatureCronJob = require('./cron-job/cron').cronJobSignature;
const backupCronJob = require('./cron-job/cron').cronJobBackup;
// router modules
const authenticateRouter = require('./routes/authenticate');
const accountRouter = require('./routes/account');
const overviewRouter = require('./routes/overview');
const userRouter = require('./routes/users');
const squadRouter = require('./routes/squads');
const rankRouter = require('./routes/ranks');
const decorationRouter = require('./routes/decorations');
const awardingRouter = require('./routes/awardings');
const requestRouter = require('./routes/request');
const playerRouter = require('./routes/players');
const signatureRouter = require('./routes/signatures');
const commandRouter = require('./routes/command');
const campaignRouter = require('./routes/campaigns');
const warRouter = require('./routes/wars');
const logRouter = require('./routes/logs');
// Configuration ***********************************
// mongoose promise setup
mongoose.Promise = global.Promise;
// app creation
const app = express();
// Middlewares *************************************************
// setup CORS-middleware
const corsOptions = {
methods: ['GET'],
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
app.use(favicon(path.join(__dirname + '/..', 'public', 'favicon.ico')));
app.use(express.static(path.join(__dirname + '/..', 'public')));
app.use(bodyParser.json({limit: '10mb'}));
app.use(bodyParser.urlencoded({limit: '10mb', extended: true}));
// API request checks for API-version and JSON etc.
app.use(restAPIchecks);
// Routes ******************************************************
app.use(urls.signatures, signatureRouter);
// initialize logging at this point to exclude /signature requests
if (process.env.NODE_ENV === config.dev.env) {
// development logging
app.use(requestLogger('dev'));
} else if (process.env.NODE_ENV !== config.test.env) {
// production logging, apache style
app.use(requestLogger(':date[clf] :method :url :response-time ms :status'));
}
app.use(urls.auth, authenticateRouter);
app.use(urls.overview, overviewRouter);
app.use(urls.users, userRouter);
app.use(urls.squads, squadRouter);
app.use(urls.ranks, rankRouter);
app.use(urls.decorations, decorationRouter);
app.use(urls.request, requestRouter);
app.use(urls.awards, awardingRouter);
app.use(urls.wars, warRouter);
app.use(urls.players, playerRouter);
app.use(urls.campaigns, campaignRouter);
app.use(urls.logs, logRouter);
app.use(urls.command, apiAuthenticationMiddleware, checkAdmin, commandRouter);
app.use(urls.account, apiAuthenticationMiddleware, checkAdmin, accountRouter);
// send index.html on all different paths
app.use(function (req, res) {
res.sendFile("public/index.html", {root: __dirname + '/..'});
});
// register error handlers
errorResponseWare(app);
// Start the server
if (process.env.NODE_ENV !== config.test.env) {
const mongoosePromise = mongoose.connect(config.database.uri + config.database.db);
mongoosePromise.then((db) => {
app.listen(config.port, (err) => {
if (err !== undefined) {
error('Error on startup, ', err);
}
else {
logger('Listening on port ' + config.port);
signatureCronJob.start();
backupCronJob.start();
}
});
})
} else {
const mongoosePromise = mongoose.connect(config.database.uri + config.test.db);
mongoosePromise.then((db) => {
app.listen(config.test.port);
logger('Listening on port ' + config.test.port);
})
}
module.exports = app;

110
api/test/awardings.spec.js Normal file
View File

@ -0,0 +1,110 @@
let mongoose = require("mongoose");
let AwardingModel = require('../models/awarding');
let urls = require('../config/api-url');
let codes = require('../routes/http-codes');
//Require the dev-dependencies
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server');
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Awardings', () => {
beforeEach((done) => { //Before each test we empty the database
AwardingModel.remove({}, (err) => {
done();
});
});
/*
* Test the /GET awardings
*/
describe('/GET awardings', () => {
it('it should GET all awardings', (done) => {
chai.request(server)
.get(urls.awards)
.end((err, res) => {
res.should.have.status(codes.success);
res.body.should.be.a('array');
res.body.length.should.be.eql(0);
done();
});
});
});
/*
* Test the /POST awardings
*/
describe('/POST awardings', () => {
it('it should not POST an awarding without auth-token provided', (done) => {
chai.request(server)
.post(urls.awards)
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /PATCH awardings
*/
describe('/PATCH awardings', () => {
it('it should not PATCH an awarding without auth-token provided', (done) => {
chai.request(server)
.patch(urls.awards + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /DELETE awardings
*/
describe('/DELETE awardings', () => {
it('it should not accept DELETE method without id in url', (done) => {
chai.request(server)
.delete(urls.awards)
.send({})
.end((err, res) => {
res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object');
res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.awards);
done();
});
});
it('it should not DELETE an awarding without auth-token provided', (done) => {
chai.request(server)
.delete(urls.awards + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
});

39
api/test/command.spec.js Normal file
View File

@ -0,0 +1,39 @@
let mongoose = require("mongoose");
let AwardingModel = require('../models/awarding');
let urls = require('../config/api-url');
let codes = require('../routes/http-codes');
//Require the dev-dependencies
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server');
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Command', () => {
beforeEach((done) => { //Before each test we empty the database
AwardingModel.remove({}, (err) => {
done();
});
});
/*
* Test the /GET awardings
*/
describe('/POST command to create signature', () => {
it('it should not succeed without auth-token provided', (done) => {
chai.request(server)
.post(urls.cmdCreateSig + "/someId")
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
});

View File

@ -1,3 +1,3 @@
const config = require('../../config/config');
process.env.NODE_ENV = config.test.unit.env;
process.env.NODE_ENV = config.test.env;

View File

@ -20,11 +20,10 @@ mongoose.connection.on('connected', () => {
});
const createString = (possible, length) => {
let text = '';
let text = "";
for (let i = 0; i < length; i++) {
for (let i = 0; i < length; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
@ -35,19 +34,21 @@ for (let i = 0; i < quantity; i++) {
UserModel.create({
username: createString('abcdefghijklmnopqrstuvwxyz0123456789', 10),
squadId: squadId,
rankLvl: Math.floor(Math.random() * 22),
}, (err, user) => {
rankLvl: Math.floor(Math.random() * 22)
}, function (err, user) {
if (err) {
console.log(err);
} else {
console.log('User created: ' + user);
}
});
})
}
// If the Node process ends, close the Mongoose connection
process.on('SIGINT', () => {
mongoose.connection.close(() => {
mongoose.connection.close(function () {
console.log('Mongoose default connection disconnected through app termination');
process.exit(0);
});

View File

@ -0,0 +1,109 @@
let mongoose = require("mongoose");
let DecorationModel = require('../models/decoration');
let urls = require('../config/api-url');
let codes = require('../routes/http-codes');
//Require the dev-dependencies
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server');
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Decorations', () => {
beforeEach((done) => { //Before each test we empty the database
DecorationModel.remove({}, (err) => {
done();
});
});
/*
* Test the /GET decorations
*/
describe('/GET decorations', () => {
it('it should GET all the decorations', (done) => {
chai.request(server)
.get(urls.decorations)
.end((err, res) => {
res.should.have.status(codes.success);
res.body.should.be.a('array');
res.body.length.should.be.eql(0);
done();
});
});
});
/*
* Test the /POST decorations
*/
describe('/POST decorations', () => {
it('it should not POST a decoration without auth-token provided', (done) => {
chai.request(server)
.post(urls.decorations)
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /PATCH decoration
*/
describe('/PATCH decorations', () => {
it('it should not PATCH a decoration without auth-token provided', (done) => {
chai.request(server)
.patch(urls.decorations + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /DELETE decorations
*/
describe('/DELETE decorations', () => {
it('it should not accept DELETE method without id in url', (done) => {
chai.request(server)
.delete(urls.decorations)
.send({})
.end((err, res) => {
res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object');
res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.decorations);
done();
});
});
it('it should not DELETE a decoration without auth-token provided', (done) => {
chai.request(server)
.delete(urls.decorations + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
});

110
api/test/ranks.spec.js Normal file
View File

@ -0,0 +1,110 @@
let mongoose = require("mongoose");
let RankModel = require('../models/rank');
let urls = require('../config/api-url');
let codes = require('../routes/http-codes');
//Require the dev-dependencies
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server');
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Ranks', () => {
beforeEach((done) => { //Before each test we empty the database
RankModel.remove({}, (err) => {
done();
});
});
/*
* Test the /GET ranks
*/
describe('/GET ranks', () => {
it('it should GET all the ranks', (done) => {
chai.request(server)
.get(urls.ranks)
.end((err, res) => {
res.should.have.status(codes.success);
res.body.should.be.a('array');
res.body.length.should.be.eql(0);
done();
});
});
});
/*
* Test the /POST ranks
*/
describe('/POST ranks', () => {
it('it should not POST a rank without auth-token provided', (done) => {
chai.request(server)
.post(urls.ranks)
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
})
/*
* Test the /PATCH rank
*/
describe('/PATCH ranks', () => {
it('it should not PATCH a rank without auth-token provided', (done) => {
chai.request(server)
.patch(urls.ranks + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /DELETE rank
*/
describe('/DELETE ranks', () => {
it('it should not accept DELETE method without id in url', (done) => {
chai.request(server)
.delete(urls.ranks)
.send({})
.end((err, res) => {
res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object');
res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.ranks);
done();
});
});
it('it should not DELETE a rank without auth-token provided', (done) => {
chai.request(server)
.delete(urls.ranks + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
});

109
api/test/squads.spec.js Normal file
View File

@ -0,0 +1,109 @@
let mongoose = require("mongoose");
let SquadModel = require('../models/squad');
let urls = require('../config/api-url');
let codes = require('../routes/http-codes');
//Require the dev-dependencies
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server');
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Squads', () => {
beforeEach((done) => { //Before each test we empty the database
SquadModel.remove({}, (err) => {
done();
});
});
/*
* Test the /GET route
*/
describe('/GET squads', () => {
it('it should GET all the squads', (done) => {
chai.request(server)
.get(urls.squads)
.end((err, res) => {
res.should.have.status(codes.success);
res.body.should.be.a('array');
res.body.length.should.be.eql(0);
done();
});
});
});
/*
* Test the /POST squad
*/
describe('/POST squads', () => {
it('it should not POST a squad without auth-token provided', (done) => {
chai.request(server)
.post(urls.squads)
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /PATCH squad
*/
describe('/PATCH squads', () => {
it('it should not PATCH a squad without auth-token provided', (done) => {
chai.request(server)
.patch(urls.squads + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /DELETE squad
*/
describe('/DELETE squads', () => {
it('it should not accept DELETE method without id in url', (done) => {
chai.request(server)
.delete(urls.squads)
.send({})
.end((err, res) => {
res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object');
res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.squads);
done();
});
});
it('it should not DELETE a squad without auth-token provided', (done) => {
chai.request(server)
.delete(urls.squads + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
});

View File

@ -1,4 +1,6 @@
let mongoose = require("mongoose");
let UserModel = require('../models/user');
let AppUserModel = require('../models/app-user');
let urls = require('../config/api-url');
let codes = require('../routes/http-codes');
@ -7,15 +9,13 @@ let codes = require('../routes/http-codes');
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server');
// chai methods
require('chai').should();
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Users', () => {
beforeEach((done) => { //Before each test we empty the database
UserModel.deleteMany({}, (err) => {
UserModel.remove({}, (err) => {
done();
});
});
@ -39,6 +39,7 @@ describe('Users', () => {
* Test the /POST route
*/
describe('/POST users', () => {
// let token;
//
// before(function (done) {
@ -102,13 +103,14 @@ describe('Users', () => {
});
/*
* Test the /PUT route
* Test the /PATCH route
*/
describe('/PUT users', () => {
it('it should not PUT a user without auth-token provided', (done) => {
describe('/PATCH users', () => {
it('it should not PATCH a user without auth-token provided', (done) => {
chai.request(server)
.put(urls.users + '/someId')
.send({_id: 'someId'})
.patch(urls.users + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
@ -117,6 +119,7 @@ describe('Users', () => {
done();
});
});
});
@ -149,5 +152,7 @@ describe('Users', () => {
done();
});
});
});
});

85
api/test/wars.spec.js Normal file
View File

@ -0,0 +1,85 @@
let mongoose = require("mongoose");
let AwardingModel = require('../models/awarding');
let urls = require('../config/api-url');
let codes = require('../routes/http-codes');
//Require the dev-dependencies
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server');
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Wars', () => {
/*
* Test the /GET awardings
*/
describe('/GET wars', () => {
it('it should GET all wars', (done) => {
chai.request(server)
.get(urls.wars)
.end((err, res) => {
res.should.have.status(codes.success);
res.body.should.be.a('array');
res.body.length.should.be.eql(0);
done();
});
});
});
/*
* Test the /POST awardings
*/
describe('/POST wars', () => {
it('it should not POST a war without auth-token provided', (done) => {
chai.request(server)
.post(urls.wars)
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
/*
* Test the /DELETE awardings
*/
describe('/DELETE wars', () => {
it('it should not accept DELETE method without id in url', (done) => {
chai.request(server)
.delete(urls.wars)
.send({})
.end((err, res) => {
res.should.have.status(codes.wrongmethod);
res.body.should.be.a('object');
res.body.should.have.property('error').property('message')
.eql('this method is not allowed at ' + urls.wars);
done();
});
});
it('it should not DELETE an awarding without auth-token provided', (done) => {
chai.request(server)
.delete(urls.wars + '/someId')
.send({})
.end((err, res) => {
res.should.have.status(codes.forbidden);
res.body.should.be.a('object');
res.body.should.have.property('success').eql(false);
res.body.should.have.property('message').eql('No token provided.');
done();
});
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,813 @@
info face="DejaVu Sans" size=19 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
common lineHeight=23 base=18 scaleW=512 scaleH=512 pages=1 packed=0
page id=0 file="DEVAJU_SANS_19.png"
chars count=193
char id=0 x=298 y=0 width=13 height=20 xoffset=-1 yoffset=3 xadvance=11 page=0 chnl=0
char id=10 x=0 y=0 width=22 height=22 xoffset=-1 yoffset=-1 xadvance=19 page=0 chnl=0
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=17 xadvance=6 page=0 chnl=0
char id=33 x=500 y=42 width=4 height=16 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=0
char id=34 x=67 y=75 width=7 height=7 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=35 x=40 y=59 width=15 height=16 xoffset=0 yoffset=3 xadvance=15 page=0 chnl=0
char id=36 x=287 y=0 width=11 height=20 xoffset=1 yoffset=2 xadvance=13 page=0 chnl=0
char id=37 x=22 y=59 width=18 height=16 xoffset=0 yoffset=3 xadvance=18 page=0 chnl=0
char id=38 x=55 y=59 width=15 height=16 xoffset=0 yoffset=3 xadvance=14 page=0 chnl=0
char id=39 x=507 y=59 width=4 height=7 xoffset=1 yoffset=3 xadvance=6 page=0 chnl=0
char id=40 x=240 y=0 width=7 height=20 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0
char id=41 x=247 y=0 width=7 height=20 xoffset=0 yoffset=2 xadvance=7 page=0 chnl=0
char id=42 x=489 y=59 width=11 height=11 xoffset=-1 yoffset=3 xadvance=9 page=0 chnl=0
char id=43 x=199 y=59 width=14 height=14 xoffset=1 yoffset=5 xadvance=16 page=0 chnl=0
char id=44 x=74 y=75 width=5 height=7 xoffset=0 yoffset=14 xadvance=6 page=0 chnl=0
char id=45 x=119 y=75 width=7 height=4 xoffset=0 yoffset=10 xadvance=7 page=0 chnl=0
char id=46 x=115 y=75 width=4 height=5 xoffset=1 yoffset=14 xadvance=6 page=0 chnl=0
char id=47 x=51 y=22 width=9 height=18 xoffset=-1 yoffset=3 xadvance=6 page=0 chnl=0
char id=48 x=0 y=59 width=12 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=49 x=396 y=42 width=11 height=16 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0
char id=50 x=407 y=42 width=11 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=51 x=418 y=42 width=11 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=52 x=429 y=42 width=13 height=16 xoffset=-1 yoffset=3 xadvance=12 page=0 chnl=0
char id=53 x=442 y=42 width=11 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=54 x=453 y=42 width=12 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=55 x=465 y=42 width=11 height=16 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0
char id=56 x=476 y=42 width=12 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=57 x=488 y=42 width=12 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=58 x=430 y=59 width=4 height=12 xoffset=1 yoffset=7 xadvance=6 page=0 chnl=0
char id=59 x=152 y=59 width=5 height=15 xoffset=0 yoffset=6 xadvance=6 page=0 chnl=0
char id=60 x=171 y=59 width=14 height=14 xoffset=1 yoffset=5 xadvance=16 page=0 chnl=0
char id=61 x=30 y=75 width=14 height=8 xoffset=1 yoffset=8 xadvance=16 page=0 chnl=0
char id=62 x=185 y=59 width=14 height=14 xoffset=1 yoffset=5 xadvance=16 page=0 chnl=0
char id=63 x=12 y=59 width=10 height=16 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=0
char id=64 x=268 y=0 width=19 height=20 xoffset=0 yoffset=2 xadvance=19 page=0 chnl=0
char id=65 x=78 y=42 width=15 height=16 xoffset=-1 yoffset=3 xadvance=13 page=0 chnl=0
char id=66 x=93 y=42 width=12 height=16 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0
char id=67 x=105 y=42 width=13 height=16 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0
char id=68 x=118 y=42 width=14 height=16 xoffset=1 yoffset=3 xadvance=15 page=0 chnl=0
char id=69 x=132 y=42 width=11 height=16 xoffset=1 yoffset=3 xadvance=12 page=0 chnl=0
char id=70 x=143 y=42 width=10 height=16 xoffset=1 yoffset=3 xadvance=11 page=0 chnl=0
char id=71 x=153 y=42 width=14 height=16 xoffset=0 yoffset=3 xadvance=15 page=0 chnl=0
char id=72 x=167 y=42 width=13 height=16 xoffset=1 yoffset=3 xadvance=15 page=0 chnl=0
char id=73 x=504 y=22 width=4 height=16 xoffset=1 yoffset=3 xadvance=6 page=0 chnl=0
char id=74 x=233 y=0 width=7 height=20 xoffset=-2 yoffset=3 xadvance=6 page=0 chnl=0
char id=75 x=180 y=42 width=13 height=16 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0
char id=76 x=193 y=42 width=11 height=16 xoffset=1 yoffset=3 xadvance=11 page=0 chnl=0
char id=77 x=204 y=42 width=15 height=16 xoffset=1 yoffset=3 xadvance=17 page=0 chnl=0
char id=78 x=219 y=42 width=13 height=16 xoffset=1 yoffset=3 xadvance=15 page=0 chnl=0
char id=79 x=232 y=42 width=15 height=16 xoffset=0 yoffset=3 xadvance=15 page=0 chnl=0
char id=80 x=247 y=42 width=11 height=16 xoffset=1 yoffset=3 xadvance=11 page=0 chnl=0
char id=81 x=11 y=22 width=15 height=19 xoffset=0 yoffset=3 xadvance=15 page=0 chnl=0
char id=82 x=258 y=42 width=13 height=16 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0
char id=83 x=271 y=42 width=12 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=84 x=283 y=42 width=14 height=16 xoffset=-1 yoffset=3 xadvance=12 page=0 chnl=0
char id=85 x=297 y=42 width=13 height=16 xoffset=1 yoffset=3 xadvance=14 page=0 chnl=0
char id=86 x=310 y=42 width=15 height=16 xoffset=-1 yoffset=3 xadvance=13 page=0 chnl=0
char id=87 x=325 y=42 width=21 height=16 xoffset=-1 yoffset=3 xadvance=19 page=0 chnl=0
char id=88 x=346 y=42 width=15 height=16 xoffset=-1 yoffset=3 xadvance=13 page=0 chnl=0
char id=89 x=361 y=42 width=14 height=16 xoffset=-1 yoffset=3 xadvance=12 page=0 chnl=0
char id=90 x=375 y=42 width=13 height=16 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0
char id=91 x=254 y=0 width=7 height=20 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0
char id=92 x=60 y=22 width=9 height=18 xoffset=-1 yoffset=3 xadvance=6 page=0 chnl=0
char id=93 x=261 y=0 width=7 height=20 xoffset=0 yoffset=2 xadvance=7 page=0 chnl=0
char id=94 x=16 y=75 width=14 height=8 xoffset=1 yoffset=2 xadvance=16 page=0 chnl=0
char id=95 x=126 y=75 width=12 height=4 xoffset=-1 yoffset=20 xadvance=10 page=0 chnl=0
char id=96 x=100 y=75 width=8 height=6 xoffset=0 yoffset=1 xadvance=10 page=0 chnl=0
char id=97 x=255 y=59 width=11 height=13 xoffset=0 yoffset=6 xadvance=12 page=0 chnl=0
char id=98 x=308 y=22 width=11 height=17 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
char id=99 x=266 y=59 width=10 height=13 xoffset=0 yoffset=6 xadvance=10 page=0 chnl=0
char id=100 x=319 y=22 width=11 height=17 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
char id=101 x=276 y=59 width=12 height=13 xoffset=0 yoffset=6 xadvance=12 page=0 chnl=0
char id=102 x=330 y=22 width=9 height=17 xoffset=0 yoffset=2 xadvance=7 page=0 chnl=0
char id=103 x=339 y=22 width=11 height=17 xoffset=0 yoffset=6 xadvance=12 page=0 chnl=0
char id=104 x=350 y=22 width=11 height=17 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
char id=105 x=361 y=22 width=4 height=17 xoffset=1 yoffset=2 xadvance=6 page=0 chnl=0
char id=106 x=39 y=0 width=6 height=21 xoffset=-1 yoffset=2 xadvance=6 page=0 chnl=0
char id=107 x=365 y=22 width=12 height=17 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=0
char id=108 x=377 y=22 width=4 height=17 xoffset=1 yoffset=2 xadvance=6 page=0 chnl=0
char id=109 x=288 y=59 width=18 height=13 xoffset=1 yoffset=6 xadvance=19 page=0 chnl=0
char id=110 x=306 y=59 width=11 height=13 xoffset=1 yoffset=6 xadvance=12 page=0 chnl=0
char id=111 x=317 y=59 width=12 height=13 xoffset=0 yoffset=6 xadvance=12 page=0 chnl=0
char id=112 x=381 y=22 width=11 height=17 xoffset=1 yoffset=6 xadvance=12 page=0 chnl=0
char id=113 x=392 y=22 width=11 height=17 xoffset=0 yoffset=6 xadvance=12 page=0 chnl=0
char id=114 x=329 y=59 width=8 height=13 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=0
char id=115 x=337 y=59 width=10 height=13 xoffset=0 yoffset=6 xadvance=10 page=0 chnl=0
char id=116 x=388 y=42 width=8 height=16 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0
char id=117 x=347 y=59 width=11 height=13 xoffset=1 yoffset=6 xadvance=13 page=0 chnl=0
char id=118 x=358 y=59 width=13 height=13 xoffset=-1 yoffset=6 xadvance=11 page=0 chnl=0
char id=119 x=371 y=59 width=17 height=13 xoffset=-1 yoffset=6 xadvance=16 page=0 chnl=0
char id=120 x=388 y=59 width=13 height=13 xoffset=-1 yoffset=6 xadvance=11 page=0 chnl=0
char id=121 x=403 y=22 width=13 height=17 xoffset=0 yoffset=6 xadvance=11 page=0 chnl=0
char id=122 x=401 y=59 width=10 height=13 xoffset=0 yoffset=6 xadvance=10 page=0 chnl=0
char id=123 x=45 y=0 width=10 height=21 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
char id=124 x=22 y=0 width=4 height=22 xoffset=1 yoffset=2 xadvance=6 page=0 chnl=0
char id=125 x=55 y=0 width=10 height=21 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
char id=126 x=79 y=75 width=14 height=7 xoffset=1 yoffset=8 xadvance=16 page=0 chnl=0
char id=160 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=17 xadvance=6 page=0 chnl=0
char id=161 x=504 y=42 width=4 height=16 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=0
char id=162 x=501 y=0 width=10 height=19 xoffset=1 yoffset=3 xadvance=12 page=0 chnl=0
char id=163 x=70 y=59 width=11 height=16 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=164 x=213 y=59 width=14 height=14 xoffset=-1 yoffset=5 xadvance=12 page=0 chnl=0
char id=165 x=81 y=59 width=14 height=16 xoffset=-1 yoffset=3 xadvance=12 page=0 chnl=0
char id=166 x=26 y=22 width=4 height=19 xoffset=1 yoffset=3 xadvance=6 page=0 chnl=0
char id=167 x=30 y=22 width=10 height=19 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=0
char id=168 x=138 y=75 width=8 height=4 xoffset=1 yoffset=2 xadvance=10 page=0 chnl=0
char id=169 x=416 y=22 width=16 height=17 xoffset=2 yoffset=2 xadvance=20 page=0 chnl=0
char id=170 x=434 y=59 width=9 height=12 xoffset=-1 yoffset=3 xadvance=8 page=0 chnl=0
char id=171 x=443 y=59 width=11 height=12 xoffset=0 yoffset=6 xadvance=11 page=0 chnl=0
char id=172 x=44 y=75 width=15 height=8 xoffset=1 yoffset=8 xadvance=17 page=0 chnl=0
char id=173 x=119 y=75 width=7 height=4 xoffset=0 yoffset=10 xadvance=7 page=0 chnl=0
char id=174 x=432 y=22 width=16 height=17 xoffset=2 yoffset=2 xadvance=20 page=0 chnl=0
char id=175 x=146 y=75 width=8 height=4 xoffset=1 yoffset=2 xadvance=10 page=0 chnl=0
char id=176 x=59 y=75 width=8 height=8 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=0
char id=177 x=227 y=59 width=14 height=14 xoffset=1 yoffset=5 xadvance=16 page=0 chnl=0
char id=178 x=500 y=59 width=7 height=10 xoffset=0 yoffset=3 xadvance=7 page=0 chnl=0
char id=179 x=0 y=75 width=8 height=10 xoffset=-1 yoffset=3 xadvance=7 page=0 chnl=0
char id=180 x=108 y=75 width=7 height=6 xoffset=2 yoffset=1 xadvance=10 page=0 chnl=0
char id=181 x=448 y=22 width=12 height=17 xoffset=1 yoffset=6 xadvance=13 page=0 chnl=0
char id=182 x=40 y=22 width=11 height=19 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
char id=183 x=115 y=75 width=4 height=5 xoffset=1 yoffset=8 xadvance=6 page=0 chnl=0
char id=184 x=93 y=75 width=7 height=7 xoffset=1 yoffset=17 xadvance=10 page=0 chnl=0
char id=185 x=8 y=75 width=8 height=10 xoffset=-1 yoffset=3 xadvance=7 page=0 chnl=0
char id=186 x=454 y=59 width=10 height=12 xoffset=-1 yoffset=3 xadvance=9 page=0 chnl=0
char id=187 x=464 y=59 width=11 height=12 xoffset=1 yoffset=6 xadvance=12 page=0 chnl=0
char id=188 x=69 y=22 width=20 height=18 xoffset=-1 yoffset=2 xadvance=18 page=0 chnl=0
char id=189 x=89 y=22 width=19 height=18 xoffset=-1 yoffset=2 xadvance=18 page=0 chnl=0
char id=190 x=108 y=22 width=21 height=18 xoffset=-1 yoffset=2 xadvance=19 page=0 chnl=0
char id=191 x=95 y=59 width=10 height=16 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=0
char id=192 x=311 y=0 width=15 height=20 xoffset=-1 yoffset=-1 xadvance=13 page=0 chnl=0
char id=193 x=326 y=0 width=15 height=20 xoffset=-1 yoffset=-1 xadvance=13 page=0 chnl=0
char id=194 x=341 y=0 width=15 height=20 xoffset=-1 yoffset=-1 xadvance=13 page=0 chnl=0
char id=195 x=65 y=0 width=16 height=21 xoffset=-2 yoffset=-2 xadvance=12 page=0 chnl=0
char id=196 x=356 y=0 width=16 height=20 xoffset=-1 yoffset=-1 xadvance=14 page=0 chnl=0
char id=197 x=81 y=0 width=15 height=21 xoffset=-1 yoffset=-2 xadvance=13 page=0 chnl=0
char id=198 x=105 y=59 width=20 height=16 xoffset=-2 yoffset=3 xadvance=18 page=0 chnl=0
char id=199 x=372 y=0 width=13 height=20 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0
char id=200 x=385 y=0 width=11 height=20 xoffset=1 yoffset=-1 xadvance=12 page=0 chnl=0
char id=201 x=396 y=0 width=11 height=20 xoffset=1 yoffset=-1 xadvance=12 page=0 chnl=0
char id=202 x=407 y=0 width=11 height=20 xoffset=1 yoffset=-1 xadvance=12 page=0 chnl=0
char id=203 x=418 y=0 width=11 height=20 xoffset=1 yoffset=-1 xadvance=12 page=0 chnl=0
char id=204 x=429 y=0 width=7 height=20 xoffset=-1 yoffset=-1 xadvance=6 page=0 chnl=0
char id=205 x=436 y=0 width=7 height=20 xoffset=0 yoffset=-1 xadvance=6 page=0 chnl=0
char id=206 x=443 y=0 width=8 height=20 xoffset=-1 yoffset=-1 xadvance=6 page=0 chnl=0
char id=207 x=451 y=0 width=8 height=20 xoffset=0 yoffset=-1 xadvance=7 page=0 chnl=0
char id=208 x=125 y=59 width=16 height=16 xoffset=0 yoffset=3 xadvance=16 page=0 chnl=0
char id=209 x=96 y=0 width=13 height=21 xoffset=1 yoffset=-2 xadvance=15 page=0 chnl=0
char id=210 x=109 y=0 width=15 height=21 xoffset=0 yoffset=-2 xadvance=15 page=0 chnl=0
char id=211 x=124 y=0 width=15 height=21 xoffset=0 yoffset=-2 xadvance=15 page=0 chnl=0
char id=212 x=139 y=0 width=15 height=21 xoffset=0 yoffset=-2 xadvance=15 page=0 chnl=0
char id=213 x=154 y=0 width=15 height=21 xoffset=0 yoffset=-2 xadvance=15 page=0 chnl=0
char id=214 x=459 y=0 width=15 height=20 xoffset=0 yoffset=-1 xadvance=15 page=0 chnl=0
char id=215 x=241 y=59 width=14 height=14 xoffset=1 yoffset=5 xadvance=16 page=0 chnl=0
char id=216 x=129 y=22 width=16 height=18 xoffset=-1 yoffset=2 xadvance=15 page=0 chnl=0
char id=217 x=169 y=0 width=13 height=21 xoffset=1 yoffset=-2 xadvance=14 page=0 chnl=0
char id=218 x=182 y=0 width=13 height=21 xoffset=1 yoffset=-2 xadvance=14 page=0 chnl=0
char id=219 x=195 y=0 width=13 height=21 xoffset=1 yoffset=-2 xadvance=14 page=0 chnl=0
char id=220 x=474 y=0 width=13 height=20 xoffset=1 yoffset=-1 xadvance=14 page=0 chnl=0
char id=221 x=487 y=0 width=14 height=20 xoffset=-1 yoffset=-1 xadvance=12 page=0 chnl=0
char id=222 x=141 y=59 width=11 height=16 xoffset=1 yoffset=3 xadvance=11 page=0 chnl=0
char id=223 x=460 y=22 width=12 height=17 xoffset=1 yoffset=2 xadvance=13 page=0 chnl=0
char id=224 x=145 y=22 width=11 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=225 x=156 y=22 width=11 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=226 x=167 y=22 width=11 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=227 x=472 y=22 width=11 height=17 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
char id=228 x=483 y=22 width=11 height=17 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
char id=229 x=0 y=22 width=11 height=20 xoffset=0 yoffset=-1 xadvance=12 page=0 chnl=0
char id=230 x=411 y=59 width=19 height=13 xoffset=0 yoffset=6 xadvance=19 page=0 chnl=0
char id=231 x=494 y=22 width=10 height=17 xoffset=0 yoffset=6 xadvance=10 page=0 chnl=0
char id=232 x=178 y=22 width=12 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=233 x=190 y=22 width=12 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=234 x=202 y=22 width=12 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=235 x=0 y=42 width=12 height=17 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
char id=236 x=214 y=22 width=8 height=18 xoffset=-2 yoffset=1 xadvance=6 page=0 chnl=0
char id=237 x=222 y=22 width=8 height=18 xoffset=0 yoffset=1 xadvance=6 page=0 chnl=0
char id=238 x=230 y=22 width=9 height=18 xoffset=-2 yoffset=1 xadvance=6 page=0 chnl=0
char id=239 x=12 y=42 width=8 height=17 xoffset=-1 yoffset=2 xadvance=6 page=0 chnl=0
char id=240 x=20 y=42 width=12 height=17 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
char id=241 x=32 y=42 width=11 height=17 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
char id=242 x=239 y=22 width=12 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=243 x=251 y=22 width=12 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=244 x=263 y=22 width=12 height=18 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
char id=245 x=43 y=42 width=12 height=17 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
char id=246 x=55 y=42 width=12 height=17 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
char id=247 x=475 y=59 width=14 height=12 xoffset=1 yoffset=6 xadvance=16 page=0 chnl=0
char id=248 x=157 y=59 width=14 height=15 xoffset=-1 yoffset=5 xadvance=12 page=0 chnl=0
char id=249 x=275 y=22 width=11 height=18 xoffset=1 yoffset=1 xadvance=13 page=0 chnl=0
char id=250 x=286 y=22 width=11 height=18 xoffset=1 yoffset=1 xadvance=13 page=0 chnl=0
char id=251 x=297 y=22 width=11 height=18 xoffset=1 yoffset=1 xadvance=13 page=0 chnl=0
char id=252 x=67 y=42 width=11 height=17 xoffset=1 yoffset=2 xadvance=13 page=0 chnl=0
char id=253 x=26 y=0 width=13 height=22 xoffset=0 yoffset=1 xadvance=11 page=0 chnl=0
char id=254 x=208 y=0 width=11 height=21 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
char id=255 x=219 y=0 width=14 height=21 xoffset=-1 yoffset=2 xadvance=12 page=0 chnl=0
kernings count=615
kerning first=65 second=171 amount=-1
kerning first=194 second=87 amount=-1
kerning first=86 second=58 amount=-2
kerning first=89 second=229 amount=-3
kerning first=196 second=65 amount=1
kerning first=45 second=66 amount=-1
kerning first=221 second=67 amount=-1
kerning first=45 second=71 amount=1
kerning first=187 second=88 amount=-1
kerning first=45 second=74 amount=1
kerning first=221 second=79 amount=-1
kerning first=45 second=81 amount=1
kerning first=65 second=84 amount=-1
kerning first=76 second=85 amount=-1
kerning first=45 second=86 amount=-1
kerning first=66 second=87 amount=-1
kerning first=45 second=88 amount=-1
kerning first=82 second=89 amount=-1
kerning first=65 second=192 amount=1
kerning first=84 second=97 amount=-3
kerning first=221 second=101 amount=-3
kerning first=65 second=102 amount=-1
kerning first=70 second=105 amount=-1
kerning first=89 second=250 amount=-2
kerning first=89 second=117 amount=-2
kerning first=192 second=119 amount=-1
kerning first=243 second=120 amount=-1
kerning first=82 second=253 amount=-1
kerning first=84 second=121 amount=-3
kerning first=76 second=217 amount=-1
kerning first=84 second=187 amount=-1
kerning first=192 second=193 amount=1
kerning first=65 second=194 amount=1
kerning first=221 second=196 amount=-1
kerning first=171 second=198 amount=1
kerning first=89 second=252 amount=-2
kerning first=75 second=210 amount=-1
kerning first=76 second=211 amount=-1
kerning first=75 second=212 amount=-1
kerning first=89 second=225 amount=-3
kerning first=84 second=228 amount=-2
kerning first=75 second=45 amount=-2
kerning first=84 second=231 amount=-3
kerning first=75 second=233 amount=-1
kerning first=87 second=234 amount=-1
kerning first=120 second=235 amount=-1
kerning first=80 second=244 amount=-1
kerning first=87 second=245 amount=-1
kerning first=120 second=246 amount=-1
kerning first=86 second=249 amount=-1
kerning first=192 second=253 amount=-1
kerning first=82 second=67 amount=-1
kerning first=221 second=213 amount=-1
kerning first=120 second=245 amount=-1
kerning first=87 second=111 amount=-1
kerning first=84 second=196 amount=-1
kerning first=195 second=65 amount=1
kerning first=89 second=210 amount=-1
kerning first=65 second=119 amount=-1
kerning first=192 second=121 amount=-1
kerning first=75 second=84 amount=-1
kerning first=79 second=46 amount=-1
kerning first=82 second=249 amount=-1
kerning first=80 second=101 amount=-1
kerning first=76 second=213 amount=-1
kerning first=221 second=243 amount=-3
kerning first=80 second=243 amount=-1
kerning first=70 second=225 amount=-2
kerning first=107 second=111 amount=-1
kerning first=75 second=235 amount=-1
kerning first=86 second=45 amount=-1
kerning first=87 second=242 amount=-1
kerning first=120 second=111 amount=-1
kerning first=107 second=249 amount=-1
kerning first=86 second=225 amount=-1
kerning first=193 second=84 amount=-1
kerning first=196 second=118 amount=-1
kerning first=75 second=250 amount=-1
kerning first=221 second=214 amount=-1
kerning first=195 second=119 amount=-1
kerning first=70 second=246 amount=-1
kerning first=87 second=194 amount=-1
kerning first=192 second=194 amount=1
kerning first=89 second=233 amount=-3
kerning first=120 second=244 amount=-1
kerning first=84 second=244 amount=-2
kerning first=76 second=121 amount=-2
kerning first=84 second=225 amount=-3
kerning first=76 second=219 amount=-1
kerning first=211 second=221 amount=-1
kerning first=195 second=192 amount=1
kerning first=107 second=253 amount=-1
kerning first=86 second=235 amount=-1
kerning first=86 second=250 amount=-1
kerning first=80 second=97 amount=-1
kerning first=221 second=97 amount=-3
kerning first=196 second=253 amount=-1
kerning first=82 second=234 amount=-1
kerning first=221 second=199 amount=-1
kerning first=196 second=171 amount=-1
kerning first=70 second=250 amount=-1
kerning first=86 second=227 amount=-1
kerning first=82 second=192 amount=-1
kerning first=187 second=192 amount=-1
kerning first=107 second=245 amount=-1
kerning first=193 second=65 amount=1
kerning first=84 second=117 amount=-3
kerning first=221 second=224 amount=-3
kerning first=80 second=224 amount=-1
kerning first=65 second=121 amount=-1
kerning first=89 second=235 amount=-3
kerning first=71 second=84 amount=-1
kerning first=45 second=87 amount=-1
kerning first=87 second=58 amount=-1
kerning first=89 second=212 amount=-1
kerning first=84 second=171 amount=-2
kerning first=187 second=195 amount=-1
kerning first=195 second=196 amount=1
kerning first=121 second=46 amount=-3
kerning first=89 second=199 amount=-1
kerning first=213 second=45 amount=1
kerning first=107 second=255 amount=-1
kerning first=45 second=89 amount=-2
kerning first=79 second=88 amount=-1
kerning first=187 second=84 amount=-2
kerning first=82 second=84 amount=-1
kerning first=187 second=193 amount=-1
kerning first=195 second=86 amount=-1
kerning first=196 second=193 amount=1
kerning first=65 second=221 amount=-1
kerning first=75 second=214 amount=-1
kerning first=84 second=246 amount=-2
kerning first=222 second=46 amount=-1
kerning first=195 second=121 amount=-1
kerning first=193 second=195 amount=1
kerning first=45 second=79 amount=1
kerning first=80 second=193 amount=-1
kerning first=253 second=58 amount=-1
kerning first=221 second=193 amount=-1
kerning first=171 second=89 amount=-1
kerning first=82 second=255 amount=-1
kerning first=86 second=229 amount=-1
kerning first=87 second=244 amount=-1
kerning first=194 second=192 amount=1
kerning first=193 second=193 amount=1
kerning first=89 second=79 amount=-1
kerning first=193 second=253 amount=-1
kerning first=210 second=45 amount=1
kerning first=192 second=171 amount=-1
kerning first=87 second=171 amount=-1
kerning first=45 second=210 amount=1
kerning first=195 second=194 amount=1
kerning first=88 second=67 amount=-1
kerning first=194 second=89 amount=-1
kerning first=87 second=246 amount=-1
kerning first=70 second=117 amount=-1
kerning first=82 second=232 amount=-1
kerning first=214 second=221 amount=-1
kerning first=82 second=86 amount=-1
kerning first=187 second=86 amount=-2
kerning first=221 second=105 amount=-1
kerning first=82 second=46 amount=-1
kerning first=75 second=252 amount=-1
kerning first=89 second=187 amount=-1
kerning first=211 second=88 amount=-1
kerning first=196 second=194 amount=1
kerning first=86 second=101 amount=-1
kerning first=70 second=233 amount=-1
kerning first=221 second=226 amount=-3
kerning first=80 second=65 amount=-1
kerning first=221 second=65 amount=-1
kerning first=80 second=226 amount=-1
kerning first=87 second=196 amount=-1
kerning first=192 second=196 amount=1
kerning first=102 second=46 amount=-1
kerning first=89 second=101 amount=-3
kerning first=70 second=234 amount=-1
kerning first=80 second=229 amount=-1
kerning first=214 second=88 amount=-1
kerning first=86 second=244 amount=-1
kerning first=221 second=251 amount=-2
kerning first=86 second=252 amount=-1
kerning first=214 second=45 amount=1
kerning first=76 second=84 amount=-3
kerning first=84 second=67 amount=-1
kerning first=75 second=101 amount=-1
kerning first=75 second=79 amount=-1
kerning first=84 second=111 amount=-3
kerning first=88 second=213 amount=-1
kerning first=84 second=58 amount=-2
kerning first=221 second=228 amount=-3
kerning first=80 second=228 amount=-1
kerning first=66 second=89 amount=-1
kerning first=75 second=121 amount=-1
kerning first=88 second=235 amount=-1
kerning first=82 second=242 amount=-1
kerning first=221 second=252 amount=-2
kerning first=194 second=121 amount=-1
kerning first=82 second=196 amount=-1
kerning first=187 second=196 amount=-1
kerning first=84 second=45 amount=-2
kerning first=89 second=192 amount=-1
kerning first=84 second=233 amount=-3
kerning first=89 second=195 amount=-1
kerning first=194 second=195 amount=1
kerning first=87 second=227 amount=-1
kerning first=107 second=242 amount=-1
kerning first=195 second=84 amount=-1
kerning first=84 second=253 amount=-3
kerning first=75 second=221 amount=-1
kerning first=88 second=232 amount=-1
kerning first=86 second=224 amount=-1
kerning first=196 second=89 amount=-1
kerning first=84 second=114 amount=-3
kerning first=87 second=117 amount=-1
kerning first=75 second=245 amount=-1
kerning first=120 second=234 amount=-1
kerning first=107 second=117 amount=-1
kerning first=193 second=89 amount=-1
kerning first=213 second=221 amount=-1
kerning first=76 second=87 amount=-2
kerning first=107 second=243 amount=-1
kerning first=82 second=243 amount=-1
kerning first=45 second=221 amount=-2
kerning first=221 second=249 amount=-2
kerning first=193 second=192 amount=1
kerning first=84 second=193 amount=-1
kerning first=84 second=250 amount=-3
kerning first=84 second=234 amount=-3
kerning first=45 second=211 amount=1
kerning first=192 second=221 amount=-1
kerning first=82 second=199 amount=-1
kerning first=89 second=214 amount=-1
kerning first=76 second=221 amount=-3
kerning first=75 second=244 amount=-1
kerning first=89 second=58 amount=-3
kerning first=75 second=220 amount=-1
kerning first=82 second=117 amount=-1
kerning first=192 second=87 amount=-1
kerning first=70 second=196 amount=-2
kerning first=211 second=45 amount=1
kerning first=87 second=229 amount=-1
kerning first=70 second=114 amount=-1
kerning first=107 second=251 amount=-1
kerning first=87 second=233 amount=-1
kerning first=89 second=194 amount=-1
kerning first=194 second=194 amount=1
kerning first=86 second=245 amount=-1
kerning first=193 second=119 amount=-1
kerning first=194 second=102 amount=-1
kerning first=82 second=111 amount=-1
kerning first=196 second=192 amount=1
kerning first=221 second=245 amount=-3
kerning first=193 second=196 amount=1
kerning first=89 second=224 amount=-3
kerning first=193 second=86 amount=-1
kerning first=208 second=221 amount=-1
kerning first=82 second=246 amount=-1
kerning first=221 second=235 amount=-3
kerning first=80 second=235 amount=-1
kerning first=195 second=87 amount=-1
kerning first=221 second=212 amount=-1
kerning first=255 second=58 amount=-1
kerning first=75 second=171 amount=-1
kerning first=86 second=195 amount=-1
kerning first=89 second=234 amount=-3
kerning first=81 second=45 amount=1
kerning first=192 second=65 amount=1
kerning first=221 second=45 amount=-2
kerning first=213 second=89 amount=-1
kerning first=70 second=227 amount=-2
kerning first=89 second=244 amount=-3
kerning first=86 second=255 amount=-1
kerning first=86 second=65 amount=-1
kerning first=221 second=225 amount=-3
kerning first=80 second=225 amount=-1
kerning first=75 second=85 amount=-1
kerning first=82 second=193 amount=-1
kerning first=76 second=253 amount=-2
kerning first=194 second=171 amount=-1
kerning first=89 second=171 amount=-2
kerning first=194 second=86 amount=-1
kerning first=86 second=251 amount=-1
kerning first=84 second=194 amount=-1
kerning first=210 second=46 amount=-1
kerning first=196 second=255 amount=-1
kerning first=70 second=111 amount=-1
kerning first=120 second=233 amount=-1
kerning first=84 second=115 amount=-3
kerning first=114 second=45 amount=-1
kerning first=194 second=221 amount=-1
kerning first=45 second=118 amount=-1
kerning first=89 second=196 amount=-1
kerning first=221 second=232 amount=-3
kerning first=114 second=171 amount=-1
kerning first=65 second=87 amount=-1
kerning first=88 second=199 amount=-1
kerning first=245 second=120 amount=-1
kerning first=75 second=251 amount=-1
kerning first=192 second=102 amount=-1
kerning first=70 second=101 amount=-1
kerning first=70 second=121 amount=-2
kerning first=196 second=119 amount=-1
kerning first=196 second=86 amount=-1
kerning first=89 second=105 amount=-1
kerning first=75 second=218 amount=-1
kerning first=80 second=46 amount=-3
kerning first=221 second=46 amount=-4
kerning first=70 second=58 amount=-1
kerning first=89 second=45 amount=-2
kerning first=212 second=46 amount=-1
kerning first=84 second=227 amount=-2
kerning first=70 second=194 amount=-2
kerning first=80 second=192 amount=-1
kerning first=221 second=192 amount=-1
kerning first=89 second=211 amount=-1
kerning first=45 second=214 amount=1
kerning first=107 second=246 amount=-1
kerning first=66 second=86 amount=-1
kerning first=87 second=250 amount=-1
kerning first=210 second=89 amount=-1
kerning first=76 second=220 amount=-1
kerning first=89 second=65 amount=-1
kerning first=194 second=65 amount=1
kerning first=222 second=58 amount=-1
kerning first=82 second=252 amount=-1
kerning first=193 second=121 amount=-1
kerning first=192 second=195 amount=1
kerning first=87 second=195 amount=-1
kerning first=84 second=199 amount=-1
kerning first=87 second=97 amount=-1
kerning first=187 second=66 amount=-1
kerning first=171 second=86 amount=-1
kerning first=82 second=45 amount=-1
kerning first=118 second=45 amount=-1
kerning first=89 second=251 amount=-2
kerning first=75 second=67 amount=-1
kerning first=86 second=232 amount=-1
kerning first=84 second=101 amount=-3
kerning first=120 second=101 amount=-1
kerning first=192 second=118 amount=-1
kerning first=107 second=250 amount=-1
kerning first=196 second=84 amount=-1
kerning first=65 second=193 amount=1
kerning first=75 second=111 amount=-1
kerning first=70 second=245 amount=-1
kerning first=70 second=224 amount=-2
kerning first=80 second=196 amount=-1
kerning first=89 second=228 amount=-3
kerning first=86 second=234 amount=-1
kerning first=75 second=211 amount=-1
kerning first=86 second=253 amount=-1
kerning first=84 second=99 amount=-3
kerning first=107 second=252 amount=-1
kerning first=195 second=193 amount=1
kerning first=196 second=196 amount=1
kerning first=86 second=46 amount=-2
kerning first=102 second=58 amount=-1
kerning first=82 second=87 amount=-1
kerning first=187 second=87 amount=-1
kerning first=75 second=253 amount=-1
kerning first=120 second=243 amount=-1
kerning first=187 second=89 amount=-2
kerning first=221 second=242 amount=-3
kerning first=80 second=242 amount=-1
kerning first=45 second=84 amount=-2
kerning first=76 second=214 amount=-1
kerning first=221 second=233 amount=-3
kerning first=221 second=195 amount=-1
kerning first=171 second=221 amount=-1
kerning first=66 second=221 amount=-1
kerning first=196 second=121 amount=-1
kerning first=82 second=250 amount=-1
kerning first=82 second=233 amount=-1
kerning first=196 second=102 amount=-1
kerning first=76 second=212 amount=-1
kerning first=221 second=117 amount=-2
kerning first=79 second=89 amount=-1
kerning first=70 second=226 amount=-2
kerning first=76 second=218 amount=-1
kerning first=195 second=253 amount=-1
kerning first=84 second=243 amount=-3
kerning first=88 second=79 amount=-1
kerning first=89 second=111 amount=-3
kerning first=84 second=224 amount=-2
kerning first=87 second=193 amount=-1
kerning first=75 second=255 amount=-1
kerning first=89 second=249 amount=-2
kerning first=89 second=67 amount=-1
kerning first=89 second=227 amount=-3
kerning first=193 second=255 amount=-1
kerning first=246 second=120 amount=-1
kerning first=75 second=234 amount=-1
kerning first=192 second=84 amount=-1
kerning first=87 second=101 amount=-1
kerning first=107 second=233 amount=-1
kerning first=84 second=195 amount=-1
kerning first=87 second=243 amount=-1
kerning first=213 second=46 amount=-1
kerning first=75 second=249 amount=-1
kerning first=221 second=58 amount=-3
kerning first=119 second=46 amount=-2
kerning first=79 second=45 amount=1
kerning first=194 second=253 amount=-1
kerning first=86 second=171 amount=-2
kerning first=68 second=89 amount=-1
kerning first=80 second=194 amount=-1
kerning first=221 second=194 amount=-1
kerning first=242 second=120 amount=-1
kerning first=195 second=255 amount=-1
kerning first=84 second=226 amount=-2
kerning first=210 second=88 amount=-1
kerning first=89 second=245 amount=-3
kerning first=70 second=65 amount=-2
kerning first=70 second=97 amount=-2
kerning first=70 second=228 amount=-2
kerning first=107 second=235 amount=-1
kerning first=221 second=229 amount=-3
kerning first=80 second=233 amount=-1
kerning first=66 second=171 amount=-1
kerning first=86 second=194 amount=-1
kerning first=87 second=114 amount=-1
kerning first=213 second=88 amount=-1
kerning first=221 second=234 amount=-3
kerning first=80 second=234 amount=-1
kerning first=84 second=105 amount=-1
kerning first=70 second=251 amount=-1
kerning first=194 second=255 amount=-1
kerning first=82 second=235 amount=-1
kerning first=194 second=118 amount=-1
kerning first=120 second=242 amount=-1
kerning first=87 second=224 amount=-1
kerning first=221 second=171 amount=-2
kerning first=70 second=195 amount=-2
kerning first=195 second=171 amount=-1
kerning first=114 second=46 amount=-2
kerning first=255 second=46 amount=-3
kerning first=76 second=210 amount=-1
kerning first=86 second=192 amount=-1
kerning first=84 second=65 amount=-1
kerning first=70 second=193 amount=-2
kerning first=70 second=253 amount=-2
kerning first=87 second=45 amount=-1
kerning first=194 second=84 amount=-1
kerning first=195 second=118 amount=-1
kerning first=75 second=89 amount=-1
kerning first=89 second=232 amount=-3
kerning first=71 second=89 amount=-1
kerning first=212 second=89 amount=-1
kerning first=65 second=118 amount=-1
kerning first=84 second=251 amount=-3
kerning first=87 second=226 amount=-1
kerning first=196 second=221 amount=-1
kerning first=119 second=58 amount=-1
kerning first=212 second=88 amount=-1
kerning first=89 second=46 amount=-4
kerning first=210 second=221 amount=-1
kerning first=86 second=242 amount=-1
kerning first=195 second=89 amount=-1
kerning first=193 second=102 amount=-1
kerning first=75 second=217 amount=-1
kerning first=221 second=211 amount=-1
kerning first=221 second=246 amount=-3
kerning first=65 second=86 amount=-1
kerning first=75 second=232 amount=-1
kerning first=194 second=119 amount=-1
kerning first=80 second=246 amount=-1
kerning first=102 second=171 amount=-1
kerning first=86 second=121 amount=-1
kerning first=75 second=242 amount=-1
kerning first=70 second=243 amount=-1
kerning first=193 second=87 amount=-1
kerning first=86 second=111 amount=-1
kerning first=82 second=121 amount=-1
kerning first=84 second=46 amount=-2
kerning first=88 second=233 amount=-1
kerning first=192 second=86 amount=-1
kerning first=88 second=234 amount=-1
kerning first=75 second=199 amount=-1
kerning first=45 second=213 amount=1
kerning first=68 second=221 amount=-1
kerning first=70 second=255 amount=-2
kerning first=221 second=250 amount=-2
kerning first=86 second=243 amount=-1
kerning first=65 second=196 amount=1
kerning first=87 second=65 amount=-1
kerning first=88 second=212 amount=-1
kerning first=194 second=196 amount=1
kerning first=87 second=252 amount=-1
kerning first=70 second=235 amount=-1
kerning first=107 second=121 amount=-1
kerning first=114 second=120 amount=-1
kerning first=75 second=219 amount=-1
kerning first=196 second=87 amount=-1
kerning first=102 second=45 amount=-1
kerning first=82 second=101 amount=-1
kerning first=221 second=227 amount=-3
kerning first=80 second=227 amount=-1
kerning first=75 second=243 amount=-1
kerning first=88 second=210 amount=-1
kerning first=87 second=249 amount=-1
kerning first=89 second=242 amount=-3
kerning first=70 second=232 amount=-1
kerning first=214 second=89 amount=-1
kerning first=221 second=210 amount=-1
kerning first=82 second=244 amount=-1
kerning first=211 second=89 amount=-1
kerning first=86 second=246 amount=-1
kerning first=82 second=58 amount=-1
kerning first=70 second=46 amount=-3
kerning first=211 second=46 amount=-1
kerning first=195 second=221 amount=-1
kerning first=80 second=245 amount=-1
kerning first=107 second=101 amount=-1
kerning first=193 second=221 amount=-1
kerning first=82 second=195 amount=-1
kerning first=65 second=65 amount=1
kerning first=89 second=243 amount=-3
kerning first=80 second=111 amount=-1
kerning first=86 second=117 amount=-1
kerning first=221 second=111 amount=-3
kerning first=84 second=235 amount=-3
kerning first=84 second=255 amount=-3
kerning first=84 second=229 amount=-2
kerning first=70 second=192 amount=-2
kerning first=88 second=171 amount=-1
kerning first=82 second=171 amount=-1
kerning first=193 second=171 amount=-1
kerning first=82 second=194 amount=-1
kerning first=187 second=194 amount=-1
kerning first=45 second=212 amount=1
kerning first=65 second=253 amount=-1
kerning first=82 second=245 amount=-1
kerning first=76 second=86 amount=-2
kerning first=86 second=228 amount=-1
kerning first=86 second=196 amount=-1
kerning first=86 second=233 amount=-1
kerning first=84 second=249 amount=-3
kerning first=88 second=45 amount=-1
kerning first=79 second=221 amount=-1
kerning first=88 second=211 amount=-1
kerning first=89 second=97 amount=-3
kerning first=87 second=228 amount=-1
kerning first=192 second=255 amount=-1
kerning first=171 second=84 amount=-1
kerning first=84 second=245 amount=-2
kerning first=89 second=213 amount=-1
kerning first=86 second=187 amount=-1
kerning first=75 second=117 amount=-1
kerning first=76 second=79 amount=-1
kerning first=65 second=89 amount=-1
kerning first=70 second=229 amount=-2
kerning first=253 second=46 amount=-3
kerning first=86 second=97 amount=-1
kerning first=80 second=232 amount=-1
kerning first=107 second=234 amount=-1
kerning first=70 second=244 amount=-1
kerning first=84 second=192 amount=-1
kerning first=107 second=244 amount=-1
kerning first=86 second=226 amount=-1
kerning first=70 second=249 amount=-1
kerning first=75 second=246 amount=-1
kerning first=70 second=252 amount=-1
kerning first=193 second=118 amount=-1
kerning first=87 second=232 amount=-1
kerning first=118 second=46 amount=-1
kerning first=244 second=120 amount=-1
kerning first=84 second=119 amount=-3
kerning first=89 second=193 amount=-1
kerning first=121 second=58 amount=-1
kerning first=194 second=193 amount=1
kerning first=74 second=45 amount=-1
kerning first=87 second=46 amount=-2
kerning first=70 second=242 amount=-1
kerning first=87 second=225 amount=-1
kerning first=71 second=221 amount=-1
kerning first=75 second=87 amount=-1
kerning first=212 second=221 amount=-1
kerning first=65 second=255 amount=-1
kerning first=111 second=120 amount=-1
kerning first=87 second=192 amount=-1
kerning first=192 second=192 amount=1
kerning first=75 second=213 amount=-1
kerning first=88 second=101 amount=-1
kerning first=88 second=214 amount=-1
kerning first=187 second=221 amount=-2
kerning first=82 second=221 amount=-1
kerning first=82 second=65 amount=-1
kerning first=187 second=65 amount=-1
kerning first=195 second=195 amount=1
kerning first=80 second=195 amount=-1
kerning first=208 second=89 amount=-1
kerning first=214 second=46 amount=-1
kerning first=212 second=45 amount=1
kerning first=87 second=251 amount=-1
kerning first=84 second=252 amount=-3
kerning first=76 second=255 amount=-2
kerning first=118 second=58 amount=-1
kerning first=193 second=194 amount=1
kerning first=76 second=89 amount=-3
kerning first=87 second=235 amount=-1
kerning first=82 second=251 amount=-1
kerning first=86 second=193 amount=-1
kerning first=221 second=187 amount=-1
kerning first=195 second=102 amount=-1
kerning first=120 second=232 amount=-1
kerning first=107 second=232 amount=-1
kerning first=84 second=232 amount=-3
kerning first=89 second=246 amount=-3
kerning first=221 second=244 amount=-3
kerning first=196 second=195 amount=1
kerning first=192 second=89 amount=-1
kerning first=84 second=242 amount=-2
kerning first=65 second=195 amount=1
kerning first=89 second=226 amount=-3

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

248
api/tools/log-parse-tool.js Normal file
View File

@ -0,0 +1,248 @@
'use strict';
const playerArrayContains = require('./util').playerArrayContains;
const WHITESPACE = ' ';
const parseWarLog = (lineArray, war) => {
const NAME_TOO_LONG_ERROR = 'Error: ENAMETOOLONG: name too long, open \'';
const stats = {
war: war,
clean: [],
budget: [],
points: [],
kills: [],
respawn: [],
revive: [],
flag: [],
transport: [],
players: []
};
const addPlayerIfNotExists = (inputPlayer) => {
const player = getPlayerAndFractionFromString(inputPlayer);
if (player && player.name && player.fraction && !playerArrayContains(stats.players, player)) {
player['warId'] = war._id;
stats.players.push(player);
}
};
lineArray.some(line => {
/**
* sanitize nameTooLongError coming up in first line
*/
if (line.includes(NAME_TOO_LONG_ERROR)) {
line = line.substring(line.indexOf(NAME_TOO_LONG_ERROR) + NAME_TOO_LONG_ERROR.length);
}
/**
* KILLS
*/
if (line.includes('(Abschuss)') && !line.includes('Fahrzeug')) {
stats.clean.push(line);
const shooterString = line.substring(line.lastIndexOf(' von: ') + 6, line.lastIndexOf('."'));
const shooter = getPlayerAndFractionFromString(shooterString);
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'
});
}
/**
* BUDGET
*/
else if (line.includes('(Budget)')) {
stats.clean.push(line);
const budg = line.split(WHITESPACE);
if (line.includes('Startbudget')) {
stats.war['budgetBlufor'] = transformMoneyString(budg[9]);
stats.war['budgetOpfor'] = transformMoneyString(budg[12].slice(0,-1));
// this date needs to be assigned in first place !important
const dateString = budg[0].slice(0,-1).split('/');
stats.war.date = new Date(dateString[0], dateString[1], dateString[2])
} else if (line.includes('Endbudget')) {
stats.war['endBudgetBlufor'] = transformMoneyString(budg[9].substr(1));
stats.war['endBudgetOpfor'] = transformMoneyString(budg[12].slice(0,-1));
stats.war.endDate = getFullTimeDate(war.date, budg[5]);
} else {
stats.budget.push(getBudgetEntry(budg, war._id, war.date));
}
}
/**
* FLAG
*/
else if (line.includes('(Fahne)') && !line.includes('Dominator')) {
stats.clean.push(line);
const playerName = line.substring(line.lastIndexOf('rt von ') + 7).slice(0, -2);
const flagFraction = line.includes('NATO Flagge') ? 'BLUFOR' : 'OPFOR';
const capture = !!line.includes('Flagge erobert');
stats.flag.push({
war: war._id,
time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]),
player: playerName,
flagFraction: flagFraction,
capture: capture
});
}
/**
* POINTS
*/
else if (line.includes('(Punkte)')) {
stats.clean.push(line);
const pt = line.split(WHITESPACE);
if (line.includes('Endpunktestand')) {
stats.war['ptBlufor'] = parseInt(pt[9]);
stats.war['ptOpfor'] = parseInt(pt[12].slice(0, -1));
// EXIT LOOP
return true;
} else {
stats.points.push(getPointsEntry(pt, line, war._id, war.date))
}
}
/**
* RESPAWN
*/
else if (line.includes('(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));
}
/**
* REVIVE
*/
else if (line.includes('(Revive)')) {
stats.clean.push(line);
const stabilized = !!line.includes('stabilisiert."');
const medicName = line.substring(line.lastIndexOf('wurde von ') + 10,
line.lastIndexOf(stabilized ? ' stabilisiert' : ' wiederbelebt'));
const medic = getPlayerAndFractionFromString(medicName);
const patientName = line.substring(line.lastIndexOf('--- ') + 4, line.lastIndexOf(' wurde von'));
const patient = getPlayerAndFractionFromString(patientName);
stats.revive.push({
war: war._id,
time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]),
stabilized: stabilized,
medic: medic.name,
patient: patient.name,
fraction: medic.fraction
});
}
/**
* TRANSPORT
*/
else if (line.includes('(Transport)')) {
stats.clean.push(line);
const driverString = line.substring(line.lastIndexOf('wurde von ') + 10, line.lastIndexOf(' eingeflogen'));
const driver = getPlayerAndFractionFromString(driverString);
const passengerString = line.substring(line.lastIndexOf('--- ') + 3, line.lastIndexOf(' wurde von'));
const passenger = getPlayerAndFractionFromString(passengerString);
const distance = parseInt(line.substring(line.lastIndexOf('eingeflogen (') + 13, line.lastIndexOf('m)') - 1));
stats.transport.push({
war: war._id,
time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]),
driver: driver ? driver.name : null,
passenger: passenger ? passenger.name : null,
fraction: driver ? driver.fraction : 'NONE',
distance: distance
});
}
/**
* PLAYERS
*/
else if (line.includes('(Fraktionsuebersicht)')) {
const playerString = line.substring(line.lastIndexOf('--- ') + 4, line.lastIndexOf(', PUID'));
addPlayerIfNotExists(playerString)
}
});
for (let i = 0; i < stats.players.length; i++) {
const playerName = stats.players[i].name;
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]['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]['revive'] = stats.revive.filter(rev => rev.medic === playerName && !rev.stabilized).length;
stats.players[i]['flagTouch'] = stats.flag.filter(flag => flag.player === playerName).length;
stats.players[i]['sort'] = stats.players[i]['kill'] + stats.players[i]['revive'] + stats.players[i]['flagTouch']
- stats.players[i]['friendlyFire'] - stats.players[i]['death'] - stats.players[i]['respawn']
}
stats.war.playersBlufor = stats.players.filter(player => player.fraction === 'BLUFOR').length;
stats.war.playersOpfor = stats.players.filter(player => player.fraction === 'OPFOR').length;
return stats;
};
const getRespawnEntry = (respawn, playerName, warId, warDate) => {
return {
war: warId,
time: getFullTimeDate(warDate, respawn[5]),
player: playerName
}
};
const getPointsEntry = (pt, line, warId, warDate) => {
return {
war: warId,
time: getFullTimeDate(warDate, pt[5]),
ptBlufor: parseInt(pt[10]),
ptOpfor: parseInt(pt[13].slice(0, -3)),
fraction: line.includes('Kein Dominator') ? 'NONE' : line.includes('NATO +1') ? 'BLUFOR' : 'OPFOR'
}
};
const getBudgetEntry = (budg, warId, warDate) => {
return {
war: warId,
time: getFullTimeDate(warDate, budg[5]),
fraction: budg[7] === 'NATO' ? 'BLUFOR' : 'OPFOR',
oldBudget: transformMoneyString(budg[9]),
newBudget: transformMoneyString(budg[12])
}
};
const getPlayerAndFractionFromString = (nameAndFractionString) => {
const nameArray = nameAndFractionString.split(WHITESPACE);
const fraction = nameArray[nameArray.length - 1] !== '(ENEMY)' ? nameArray[nameArray.length - 1] === '(WEST)' ? 'BLUFOR' : 'OPFOR' : undefined;
const name = nameAndFractionString.substring(0, nameAndFractionString.indexOf(nameArray[nameArray.length - 1]) - 1);
// do not return player for 'Selbstverschulden' or 'Error: No unit'
if (name && name !== 'Error: No unit') {
return {name: name, fraction: fraction};
}
};
const transformMoneyString = (budgetString) => {
if (!budgetString.includes('e+')) {
return parseInt(budgetString);
}
const budget = budgetString.split('e+');
return Math.round(parseFloat(budget[0]) * Math.pow(10, parseInt(budget[1])));
};
const getFullTimeDate = (date, timeString) => {
const returnDate = new Date(date);
const time = timeString.split(':');
returnDate.setHours(time[0]);
returnDate.setMinutes(time[1]);
return returnDate;
};
module.exports = parseWarLog;

267
api/tools/signature-tool.js Normal file
View File

@ -0,0 +1,267 @@
"use strict";
// modules used for graphic manipulation
const jimp = require('jimp');
const imagemin = require('imagemin');
const imageminpngquant = require('imagemin-pngquant');
const SHA1 = require('node-sha1');
const async = require('async');
// Mongoose Model using mongoDB
const UserModel = require('../models/user');
const AwardingModel = require('../models/awarding');
const RankModel = require('../models/rank');
// http error code table
const codes = require('../routes/http-codes');
// standard input/output file extension
const fileExt = '.png';
const resourceDir = __dirname + '/../resource/';
let createSignature = (userId, res, next) => {
let loadedImage;
let user;
if (!String(userId).match(/^[0-9a-fA-F]{24}$/)) {
let error = new Error('no valid ID ' + userId);
error.status = codes.wrongdatatyperequest;
return next(error);
}
UserModel.findById(userId, ['username', 'rankLvl', 'squadId'])
.populate('squadId', ['name', 'fraction'])
.exec()
.then((resUser) => {
user = resUser;
let platePath;
if (resUser && resUser.squadId && resUser.squadId.fraction === 'BLUFOR') {
platePath = __dirname + '/backplate/blufor' + fileExt;
} else if (resUser && resUser.squadId && resUser.squadId.fraction === 'OPFOR') {
platePath = __dirname + '/backplate/opfor' + fileExt;
}
// kill process on undefined platePath
if (!platePath) {
throw new Error('Fraction not defined for user with id ' + userId);
}
return jimp.read(platePath)
})
.then((image) => {
loadedImage = image;
}).then(() => {
return jimp.loadFont(__dirname + '/font/DEVAJU_SANS_19.fnt');
})
.then((font) => {
loadedImage.print(font, 128, 8, user.username)
})
.then(() => {
return jimp.loadFont(__dirname + '/font/DEJAVU_SANS_13.fnt');
})
.then((font) => {
loadedImage.print(font, 128, 35, user.squadId.name);
return font;
})
.then((font) => {
let rankH, rankW, rankX, rankY;
RankModel.findOne({'level': user.rankLvl, 'fraction': user.squadId.fraction}, (err, result) => {
if (err) {
return next(err)
}
if (result) {
if (user.squadId.fraction === 'BLUFOR') {
rankW = 25;
rankH = 60;
rankX = 36;
rankY = 34;
} else {
rankW = 37;
rankH = 58;
rankX = 30;
rankY = 34;
}
jimp.read(resourceDir + 'rank/' + result._id + fileExt)
.then((rankImage) => {
rankImage.resize(rankW, rankH);
loadedImage
.print(font, 128, 55, result.name)
.composite(rankImage, rankX, rankY)
})
.then(() => {
addDecorationsAndSave(userId, loadedImage, res, next);
})
} else {
// user has not any assignable rank in his fraction at this point,
// e.g. assigned rank has been deleted or switched fraction so rankLvl is not defined
addDecorationsAndSave(userId, loadedImage, res, next);
}
})
})
.catch((err) => {
next(err);
})
};
/**
* query decorations according to user id and
* add all graphics into given image
*
* @param userId
* @param loadedImage - background image
* @param res
* @param next
*/
let addDecorationsAndSave = (userId, loadedImage, res, next) => {
const medalW = 21;
const medalH = 40;
const ribbonW = 53;
const ribbonH = 15;
let medalPx = 630;
let medalPy = 5;
let ribbonPx = 598;
let ribbonPy = 95;
AwardingModel.find({
'userId': userId,
'confirmed': 1
}, ['decorationId', 'date']).populate('decorationId', ['isMedal', 'fraction'])
.exec((err, awardings) => {
if (err) {
return next(err);
}
if (awardings.length > 0) {
//TODO: simplify this sorting hell
awardings.sort((a1, a2) => {
if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) {
if (a1.decorationId.fraction === a2.decorationId.fraction) {
if (a1.date !== a2.date) {
if (a1.date < a2.date) {
return -1;
}
if (a1.date > a2.date) {
return 1;
}
}
return 0;
} else {
if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') {
return -1;
}
if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') {
return 1;
}
}
}
if (a1.decorationId.isMedal !== a2.decorationId.isMedal) {
if (a1.decorationId.isMedal && !a2.decorationId.isMedal) {
return 1;
}
if (!a1.decorationId.isMedal && a2.decorationId.isMedal) {
return -1;
}
}
if (a1.decorationId.isMedal && a2.decorationId.isMedal) {
if (a1.date !== a2.date) {
if (a1.date < a2.date) {
return -1;
}
if (a1.date > a2.date) {
return 1;
}
}
return 0;
}
});
// use synchronized call to keep correct order of decorations
async.eachSeries(awardings, (award, callback) => {
jimp.read(resourceDir + 'decoration/' + award.decorationId._id + fileExt).then((decorationImage) => {
if (award.decorationId.isMedal) {
decorationImage.resize(medalW, medalH);
loadedImage.composite(decorationImage, medalPx, medalPy);
if (medalPy === 5) {
medalPx = medalPx - 1 - medalW;
} else {
medalPx = medalPx + 1 + medalW;
}
if (medalPx <= 300) {
medalPy = medalPy + 3 + medalH;
}
} else {
decorationImage.resize(ribbonW, ribbonH);
loadedImage.composite(decorationImage, ribbonPx, ribbonPy);
ribbonPx = ribbonPx - 2 - ribbonW;
if (ribbonPx <= 154) {
ribbonPy = ribbonPy - 3 - ribbonH;
ribbonPx = 598;
}
}
callback();
})
}, (err) => {
if (err) {
throw err;
}
compareImagesAndSave(loadedImage, userId, res, next);
});
} else {
compareImagesAndSave(loadedImage, userId, res, next);
}
}
)
};
let compareImagesAndSave = (generatedImage, userId, res, next) => {
return jimp.read(resourceDir + 'signature/big/' + userId + fileExt)
.then((oldImage) => {
// compare hashes of image map to recognize difference
const sig1 = SHA1(generatedImage.bitmap.data);
const sig2 = SHA1(oldImage.bitmap.data);
if (sig1 !== sig2) {
saveJimpImageAndCompress(generatedImage, userId, res, next)
} else {
res.locals.items = {status: 'nothing to do'};
next();
}
})
.catch((err) => {
saveJimpImageAndCompress(generatedImage, userId, res, next);
})
};
/**
*
* Write Jimp image to file system
* Gets minified after 3 seconds timeout
*
* @param image - Jimp image
* @param userId - user id of signature owner
* @param res - express response object
* @param next - express next-function
*/
let saveJimpImageAndCompress = (image, userId, res, next) => {
image.write(resourceDir + 'signature/big/' + userId + fileExt);
setTimeout(() => {
imagemin([resourceDir + 'signature/big/' + userId + fileExt], resourceDir + 'signature/', {
plugins: [
imageminpngquant({quality: '65-80'})
]
}).then((files) => {
res.locals.items = {status: 'success'};
return next();
}).catch((error) => {
console.log(error)
})
}, 3000);
};
module.exports = createSignature;

View File

@ -1,9 +1,4 @@
'use strict';
const isSteamUUID = (input) => {
const steamUIDPattern = new RegExp('[0-9]{17}');
return steamUIDPattern.test(input);
};
"use strict";
const sortCollectionBy = (collection, key) => {
collection.sort((a, b) => {
@ -17,9 +12,7 @@ const sortCollectionBy = (collection, key) => {
};
const playerArrayContains = (arr, item) => {
let i = 0;
let count = arr.length;
let matchFound = false;
let i = 0, count = arr.length, matchFound = false;
for (; i < count; i++) {
if (arr[i].name === item.name && arr[i].fraction === item.fraction) {
@ -39,10 +32,11 @@ const timeStringToDecimal = (timeString) => {
return hour + (sek / 3600);
};
const decimalToTimeString = (decimal) => {
const hours = parseInt(decimal.toString().split('.')[0]);
const hours = parseInt(decimal.toString().split(".")[0]);
const minutesFloat = ((decimal % 1) * 3600) / 60;
const minutes = parseInt(minutesFloat.toString().split('.')[0]);
const minutes = parseInt(minutesFloat.toString().split(".")[0]);
const seconds = Math.round((minutesFloat - parseInt(minutes)) * 60);
return (hours < 10 ? '0' + hours : hours) + ':' +
@ -50,16 +44,7 @@ const decimalToTimeString = (decimal) => {
(seconds < 10 ? '0' + seconds : seconds);
};
const getFileBuffer = (file) => {
if (file.encoding === 'base64') {
return Buffer.from(file.buffer.toString(), file.encoding);
}
return Buffer.from(file.buffer);
};
exports.isSteamUUID = isSteamUUID;
exports.sortCollection = sortCollectionBy;
exports.playerArrayContains = playerArrayContains;
exports.timeStringToDecimal = timeStringToDecimal;
exports.decimalToTimeString = decimalToTimeString;
exports.getFileBuffer = getFileBuffer;

View File

@ -4,18 +4,18 @@
cd $(dirname $0)
# array of available collection names
col=(app_user awarding campaign decoration logBudget logFlag logKill logPlayerCount logPoints logRespawn logRevive logServerFps logTransport logVehicle player promotion rank squad user war)
col=(app_user awarding campaign decoration logBudget logFlag logKill logPoints logRespawn logRevive logTransport player promotion rank squad user war )
if [ -z "$1" ]
then
DATE=$(date '+%Y-%m-%d')
mkdir -p ${DATE}/resource
cp -R ../server/resource/ ${DATE}/
cp -R ../api/resource/ ${DATE}/
else
DATE=${1}
tar -xzf ${DATE}.tar.gz
rm -rf ../server/resource
cp -Rv ${DATE}/resource ../server/
rm -rf ../api/resource
cp -Rv ${DATE}/resource ../api/
fi
for i in "${col[@]}"

30
docs/infra/3rd-party-install.sh Executable file → Normal file
View File

@ -2,12 +2,13 @@
cd $(dirname $0)
echo "########################################################"
echo "###### INSTALLING ALL REQUIRED 3RD-PARTY SOFTWARE ######"
echo "########################################################"
echo "# MONGO DB COMMUNITY"
echo "# NPM"
echo "# NODE"
#####################################################
###### INSTALL ALL REQUIRED 3RD-PARTY SOFTWARE ######
#####################################################
# MONGO DB COMMUNITY
# NPM
# NODE
# mongodb key & repo
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5
@ -25,26 +26,13 @@ sudo systemctl enable mongod.service
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
source ~/.nvm/nvm.sh
nvm install stable
sudo ln -sf ~/.nvm/versions/node/$(node -v)/bin/node /usr/bin/node
sudo ln -s ~/.nvm/versions/node/$(node -v)/bin/node /usr/bin/node
# upgrade to latest npm version
sudo npm install -g npm@latest
if [ "${1}" == "prod" ]; then
echo "########################################################"
echo "################ SETTING UP APPLICATION ################"
echo "########################################################"
cd ./../..
npm install
npm run deploy-static:prod
echo "########################################################"
echo "####### INSTALLING PM2 AND INGRAIN AUTO STARTUP ########"
echo "########################################################"
sudo npm install -g pm2
pm2 start pm2-start.json
pm2 save
pm2 start ./../../pm2-start.json
pm2 startup systemd
fi
echo " ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑"
echo "IMPORTANT: Run the sudo command printed above to finish the pm2 setup"

View File

@ -1,27 +0,0 @@
<code_scheme name="OPT-CC" version="173">
<option name="FORMATTER_TAGS_ENABLED" value="true" />
<JSCodeStyleSettings version="0">
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
</TypeScriptCodeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="RIGHT_MARGIN" value="120" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="WRAP_COMMENTS" value="true" />
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="WRAP_COMMENTS" value="true" />
</codeStyleSettings>
</code_scheme>

Binary file not shown.

567
package-lock.json generated
View File

@ -1,29 +1,32 @@
{
"name": "opt-cc",
"version": "1.9.3",
"version": "1.6.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz",
"integrity": "sha1-RBT/dKUIecII7l/cgm4ywwNUnto=",
"dev": true,
"requires": {
"co": "^4.6.0",
"fast-deep-equal": "^1.0.0",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.3.0"
"co": "4.6.0",
"fast-deep-equal": "1.0.0",
"fast-json-stable-stringify": "2.0.0",
"json-schema-traverse": "0.3.1"
}
},
"ansi-regex": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz",
"integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz",
"integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=",
"dev": true
},
"asn1": {
"version": "0.2.3",
@ -50,26 +53,29 @@
"dev": true
},
"aws4": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz",
"integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
"dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
"dev": true,
"optional": true,
"requires": {
"tweetnacl": "^0.14.3"
"tweetnacl": "0.14.5"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
"boom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
"dev": true,
"requires": {
"hoek": "4.2.0"
}
},
"caseless": {
"version": "0.12.0",
@ -78,24 +84,23 @@
"dev": true
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
"integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
"ansi-styles": "1.1.0",
"escape-string-regexp": "1.0.5",
"has-ansi": "0.1.0",
"strip-ansi": "0.3.0",
"supports-color": "0.2.0"
},
"dependencies": {
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz",
"integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=",
"dev": true
}
}
},
@ -105,28 +110,13 @@
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
"dev": true
},
"color-convert": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz",
"integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==",
"dev": true,
"requires": {
"color-name": "1.1.1"
}
},
"color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=",
"dev": true
},
"combined-stream": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
"integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
"delayed-stream": "1.0.0"
}
},
"commander": {
@ -136,26 +126,25 @@
"dev": true
},
"concurrently": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.6.0.tgz",
"integrity": "sha512-6XiIYtYzmGEccNZFkih5JOH92jLA4ulZArAYy5j1uDSdrPLB3KzdE8GW7t2fHPcg9ry2+5LP9IEYzXzxw9lFdA==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.5.0.tgz",
"integrity": "sha1-jPG3cHppFqeKT/W3e7BN7FSzebI=",
"dev": true,
"requires": {
"chalk": "^2.4.1",
"chalk": "0.5.1",
"commander": "2.6.0",
"date-fns": "^1.23.0",
"lodash": "^4.5.1",
"read-pkg": "^3.0.0",
"date-fns": "1.29.0",
"lodash": "4.17.4",
"rx": "2.3.24",
"spawn-command": "^0.0.2-1",
"supports-color": "^3.2.3",
"tree-kill": "^1.1.0"
"spawn-command": "0.0.2-1",
"supports-color": "3.2.3",
"tree-kill": "1.2.0"
}
},
"core-js": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
"integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=",
"dev": true
},
"core-util-is": {
@ -164,13 +153,33 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"cryptiles": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
"dev": true,
"requires": {
"boom": "5.2.0"
},
"dependencies": {
"boom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
"dev": true,
"requires": {
"hoek": "4.2.0"
}
}
}
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
"assert-plus": "1.0.0"
}
},
"date-fns": {
@ -192,16 +201,7 @@
"dev": true,
"optional": true,
"requires": {
"jsbn": "~0.1.0"
}
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
"jsbn": "0.1.1"
}
},
"escape-string-regexp": {
@ -223,9 +223,9 @@
"dev": true
},
"fast-deep-equal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
"integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=",
"dev": true
},
"fast-json-stable-stringify": {
@ -241,14 +241,14 @@
"dev": true
},
"form-data": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
"integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "1.0.6",
"mime-types": "^2.1.12"
"asynckit": "0.4.0",
"combined-stream": "1.0.5",
"mime-types": "2.1.17"
}
},
"getpass": {
@ -257,15 +257,9 @@
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
"assert-plus": "1.0.0"
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@ -278,26 +272,41 @@
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
"dev": true,
"requires": {
"ajv": "^5.1.0",
"har-schema": "^2.0.0"
"ajv": "5.3.0",
"har-schema": "2.0.0"
}
},
"has-ansi": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz",
"integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=",
"dev": true,
"requires": {
"ansi-regex": "0.2.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
"dev": true
},
"hawk": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
"dev": true,
"requires": {
"boom": "4.3.1",
"cryptiles": "3.1.2",
"hoek": "4.2.0",
"sntp": "2.1.0"
}
},
"hoek": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==",
"dev": true
},
"hosted-git-info": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.1.tgz",
"integrity": "sha512-Ba4+0M4YvIDUUsprMjhVTU1yN9F2/LJSAl69ZpzaLT4l4j5mwTS6jqqW9Ojvj6lKz/veqPzpJBqGbXspOb533A==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
"integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==",
"dev": true
},
"http-signature": {
@ -306,24 +315,9 @@
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
}
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-builtin-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
"dev": true,
"requires": {
"builtin-modules": "^1.0.0"
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.13.1"
}
},
"is-typedarray": {
@ -356,11 +350,11 @@
"integrity": "sha1-M4WseQGSEwy+Iw6ALsAskhW7/to=",
"dev": true,
"requires": {
"hoek": "4.x.x",
"isemail": "2.x.x",
"items": "2.x.x",
"moment": "2.x.x",
"topo": "2.x.x"
"hoek": "4.2.0",
"isemail": "2.2.1",
"items": "2.1.1",
"moment": "2.19.1",
"topo": "2.0.2"
}
},
"jsbn": {
@ -370,12 +364,6 @@
"dev": true,
"optional": true
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
@ -406,37 +394,25 @@
"verror": "1.10.0"
}
},
"load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0",
"strip-bom": "^3.0.0"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
"dev": true
},
"mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
"integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=",
"dev": true
},
"mime-types": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
"version": "2.1.17",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
"integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
"dev": true,
"requires": {
"mime-db": "~1.33.0"
"mime-db": "1.30.0"
}
},
"minimist": {
@ -446,60 +422,23 @@
"dev": true
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=",
"version": "2.19.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.19.1.tgz",
"integrity": "sha1-VtoaLRy/AdOLfhr8McELz6GSkWc=",
"dev": true
},
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
"dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
"is-builtin-module": "^1.0.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
},
"oauth-sign": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
"dev": true
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"dev": true,
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
}
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"dev": true,
"requires": {
"pify": "^3.0.0"
}
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
@ -507,48 +446,39 @@
"dev": true
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
"dev": true
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
"integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
"dev": true,
"requires": {
"load-json-file": "^4.0.0",
"normalize-package-data": "^2.3.2",
"path-type": "^3.0.0"
}
},
"request": {
"version": "2.87.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz",
"integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==",
"version": "2.83.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
"integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.6.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.5",
"extend": "~3.0.1",
"forever-agent": "~0.6.1",
"form-data": "~2.3.1",
"har-validator": "~5.0.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.17",
"oauth-sign": "~0.8.2",
"performance-now": "^2.1.0",
"qs": "~6.5.1",
"safe-buffer": "^5.1.1",
"tough-cookie": "~2.3.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.1.0"
"aws-sign2": "0.7.0",
"aws4": "1.6.0",
"caseless": "0.12.0",
"combined-stream": "1.0.5",
"extend": "3.0.1",
"forever-agent": "0.6.1",
"form-data": "2.3.1",
"har-validator": "5.0.3",
"hawk": "6.0.2",
"http-signature": "1.2.0",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.17",
"oauth-sign": "0.8.2",
"performance-now": "2.1.0",
"qs": "6.5.1",
"safe-buffer": "5.1.1",
"stringstream": "0.0.5",
"tough-cookie": "2.3.3",
"tunnel-agent": "0.6.0",
"uuid": "3.1.0"
}
},
"rx": {
@ -558,22 +488,19 @@
"dev": true
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true
"sntp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
"integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
"dev": true,
"requires": {
"hoek": "4.2.0"
}
},
"spawn-command": {
"version": "0.0.2-1",
@ -581,76 +508,44 @@
"integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
"dev": true
},
"spdx-correct": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
"integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
"dev": true,
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-exceptions": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
"integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==",
"dev": true
},
"spdx-expression-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
"integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
"dev": true,
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-license-ids": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz",
"integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==",
"dev": true
},
"sshpk": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
"integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
"integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
"dev": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
"asn1": "0.2.3",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.1",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.1",
"getpass": "0.1.7",
"jsbn": "0.1.1",
"tweetnacl": "0.14.5"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
"dev": true
},
"strip-ansi": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz",
"integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=",
"dev": true,
"requires": {
"ansi-regex": "0.2.1"
}
},
"supports-color": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
"integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
"dev": true,
"requires": {
"has-flag": "^1.0.0"
},
"dependencies": {
"has-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
"dev": true
}
"has-flag": "1.0.0"
}
},
"topo": {
@ -659,16 +554,16 @@
"integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=",
"dev": true,
"requires": {
"hoek": "4.x.x"
"hoek": "4.2.0"
}
},
"tough-cookie": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
"integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
"dev": true,
"requires": {
"punycode": "^1.4.1"
"punycode": "1.4.1"
}
},
"tree-kill": {
@ -683,7 +578,7 @@
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"dev": true,
"requires": {
"safe-buffer": "^5.0.1"
"safe-buffer": "5.1.1"
}
},
"tweetnacl": {
@ -694,43 +589,33 @@
"optional": true
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz",
"integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==",
"dev": true,
"requires": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"assert-plus": "1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
"extsprintf": "1.3.0"
}
},
"wait-on": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-2.1.0.tgz",
"integrity": "sha512-hDwJ674+7dfiiK/cxtYCwPxlnjXDjto/pCz1PF02sXUhqCqCWsgvxZln0699PReWqXXgkxqkF6DDo5Rj9sjNvw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-2.0.2.tgz",
"integrity": "sha1-CoT9BwJMb8Joyw6r5YW+IXqvK6o=",
"dev": true,
"requires": {
"core-js": "^2.4.1",
"joi": "^9.2.0",
"minimist": "^1.2.0",
"request": "^2.78.0",
"rx": "^4.1.0"
"core-js": "2.5.1",
"joi": "9.2.0",
"minimist": "1.2.0",
"request": "2.83.0",
"rx": "4.1.0"
},
"dependencies": {
"rx": {

View File

@ -1,23 +1,20 @@
{
"name": "opt-cc",
"version": "1.9.7",
"version": "1.7.0",
"license": "MIT",
"author": "Florian Hartwich <hardi@noarch.de>",
"private": true,
"scripts": {
"start": "npm run deploy-static-prod && npm start --prefix ./server",
"dev": "npm run deploy-static && npm run dev --prefix ./server",
"pre-deploy-clean": "rm -f ./public/resource",
"deploy-static": "npm run pre-deploy-clean && npm run build --prefix=static && npm run deploy-static:link-resource && npm run deploy-static:api-docs",
"deploy-static:prod": "npm run pre-deploy-clean && npm run build:prod --prefix=static && npm run deploy-static:link-resource && npm run deploy-static:api-docs",
"deploy-static:link-resource": "ln -s ../server/resource/ public/resource",
"deploy-static:api-docs": "npm run api:docs --prefix=server",
"postinstall": "npm install --prefix ./static && npm install --prefix ./server",
"lint": "npm run lint --prefix=server && npm run lint --prefix=static",
"test": "npm test --prefix ./server",
"e2e": "npm run deploy-static && concurrently \"npm run start-test --prefix ./server\" \"wait-on -t 60000 http://localhost:3001/ && npm run e2e --prefix ./static\" --success first --kill-others",
"start-e2e": "npm run deploy-static && npm run start-test --prefix ./server",
"test-e2e": "npm run e2e --prefix ./static",
"test-api": "npm run api:test-docs --prefix ./server"
"start": "npm run deploy-static-prod && npm start --prefix ./api",
"dev": "npm run deploy-static && npm run dev --prefix ./api",
"deploy-static": "cd ./static && $(npm bin)/ng build && ln -s ../api/resource/ ../public/resource",
"deploy-static:prod": "cd ./static && $(npm bin)/ng build --prod --aot && ln -s ../api/resource/ ../public/resource",
"postinstall": "npm install --prefix ./static && npm install --prefix ./api",
"mongodb": "mkdir -p mongodb-data && mongod --dbpath ./mongodb-data",
"test": "npm test --prefix ./api",
"e2e": "npm run deploy-static && concurrently \"npm run e2e --prefix ./api\" \"wait-on -t 60000 http://localhost:3001/ && npm run e2e --prefix ./static\" --success first --kill-others",
"start-e2e": "npm run deploy-static && npm run e2e --prefix ./api",
"test-e2e": "npm run e2e --prefix ./static"
},
"dependencies": {},
"devDependencies": {

View File

@ -2,7 +2,7 @@
"apps": [
{
"name": "opt-cc",
"script": "./server/server.js",
"script": "./api/server.js",
"watch": false,
"env": {
"NODE_ENV": "production",

View File

@ -1,11 +0,0 @@
module.exports = {
'extends': 'google',
'parserOptions': {
'ecmaVersion': 6
},
'rules': {
'max-len': [2, 120, 4, {
'ignoreUrls': true
}],
}
};

View File

@ -1,39 +0,0 @@
### List App Users [GET /account]
List all app users, ordered by username
**Permission: 4**
+ Response 200 (application/json; charset=utf-8)
+ Attributes (array[AppUser], fixed-type)
### Update App User [PATCH /account/{id}]
Update an app user, identified by its id
**Permission: 4**
+ Parameters
+ id: `5abf5064861d950f157c4a09` (string, required) - unique id of app user
+ Request (application/json)
+ Attributes (UpdateAppUser)
+ Response 200 (application/json; charset=utf-8)
+ Attributes (AppUser, fixed-type)
### Delete App User [DELETE /account/{id}]
**Permission: 4**
Delete an app user
+ Parameters
+ id: `5ac0de67b5edc7771c027b94` (string, required) - unique id of app user
+ Response 204

View File

@ -1,19 +0,0 @@
### Update signature image [POST /cmd/createSignature/{userId}]
Update an army members signature image
**Permission: 4**
+ Parameters
+ userId: `5ab68d42f547ed304064e5f7` (string, required) - army members unique user id
+ Request (application/json)
+ Attributes (object)
+ Response 200 (application/json; charset=utf-8)
+ Attributes
+ status: `success` (string, required) - action result message

View File

@ -1,7 +0,0 @@
### Get Army Overview [GET /overview]
Get bundled army, squad and member data for the participating fractions
+ Response 200 (application/json; charset=utf-8)
+ Attributes (array[Fraction])

View File

@ -1,63 +0,0 @@
### Get Awardings [GET /awardings{?userId,inProgress,fractFilter,squadId}]
List all awardings
+ Parameters
+ userId: `5ab68d42f547ed304064e5f7` (string, optional)
specific army member Id to show the awardings for
+ inProgress: false (boolean, optional)
true to filter by awarding state 'in progress'
+ Default: false
+ fractFilter: `BLUFOR` (enum[string], optional)
Field to filter by fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ squadId: `5aba54eaeadcce6332c6a774` (string, optional)
unique id of the squad
+ Response 200 (application/json; charset=utf-8)
+ Attributes (array[AwardingPopulated], fixed-type)
### Create Awarding [POST /awardings]
Create a new awarding which is immediatly assigned to the user
**Permission: 2**
+ Request (application/json)
+ Attributes
+ userId: `5ab68d42f547ed304064e5f7` (string, required) - unique id of the army member to give award
+ decorationId: `5abd3dff6e6a0334d95b8ba0` (string, required) - unique id of the decoration
+ reason: `Good boy` (string, required) - reason for giving the awarding
+ proposer: `5ab68ceef547ed304064e5f6` (string, required) - app user id, who requested this awarding
+ Response 201 (application/json; charset=utf-8)
+ Attributes (Awarding, fixed-type)
### Create Awarding Proposal [POST /request/award]
Create a new awarding proposal, that needs to be approved by higher permission level user to take effect
**Permission: 1**
+ Request (application/json)
+ Attributes
+ userId: `5ab68d42f547ed304064e5f7` (string, required) - unique id of the army member to give award
+ decorationId: `5abd3dff6e6a0334d95b8ba0` (string, required) - unique id of the decoration
+ reason: `Good boy` (string, required) - reason for giving the awarding
+ Response 201 (application/json; charset=utf-8)
+ Attributes (Awarding, fixed-type)

View File

@ -1,129 +0,0 @@
### List Decorations [GET /decorations{?q,fractFilter}]
List all decorations
+ Parameters
+ q: `tapferkeit` (string, optional)
Filter string for the partial decoration name
+ fractFilter: `BLUFOR` (enum[string], optional)
Field to filter by fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ Response 200 (application/json; charset=utf-8)
+ Attributes (array[Decoration], fixed-type)
### Get Decoration [GET /decorations/{id}]
Retrieve single decoration data
+ Parameters
+ id: `5abd3dff6e6a0334d95b8ba0` (string, required) - unique id of the decoration to fetch
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Decoration, fixed-type)
### Create Decoration [POST /decorations]
Create a new decoration
**Permission: 2**
+ Request (multipart/form-data; boundary=---BOUNDARY)
-----BOUNDARY
Content-Disposition: form-data; name="name"
Content-Type: text/plain
Super medal 2000
-----BOUNDARY
Content-Disposition: form-data; name="fraction"
Content-Type: text/plain
OPFOR
-----BOUNDARY
Content-Disposition: form-data; name="description"
Content-Type: text/plain
For good performance in a battle
-----BOUNDARY
Content-Disposition: form-data; name="isMedal"
Content-Type: text/plain
true
-----BOUNDARY
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
-----BOUNDARY--
+ Response 201 (application/json; charset=utf-8)
+ Attributes (Decoration, fixed-type)
### Update Decoration [PATCH /decorations/{id}]
Update decoration, identified by its id
**Permission: 2**
+ Parameters
+ id: `5abeb420b987672bb1ede643` (string, required) - unique id of the decoration
+ Request (multipart/form-data; boundary=---BOUNDARY)
-----BOUNDARY
Content-Disposition: form-data; name="name"
Content-Type: text/plain
Super medal 2000
-----BOUNDARY
Content-Disposition: form-data; name="fraction"
Content-Type: text/plain
OPFOR
-----BOUNDARY
Content-Disposition: form-data; name="description"
Content-Type: text/plain
For good performance in a battle
-----BOUNDARY
Content-Disposition: form-data; name="sortingNumber"
Content-Type: text/plain
2
-----BOUNDARY
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
-----BOUNDARY--
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Decoration, fixed-type)
### Delete Decoration [DELETE /decorations/{id}]
Delete a decoration
**Permission: 2**
+ Parameters
+ id: `5abeb43cb987672bb1ede644` (string, required) - unique id of the decoration
+ Response 204

View File

@ -1,65 +0,0 @@
### List Promotions [GET /request/promotion{?squadId,inProgress,fractFilter}]
List all promotion requests
+ Parameters
+ squadId: `591470249e9fae286e008e31` (string, optional)
specific squad Id to show the promotion requests for
+ inProgress: false (boolean, optional)
true to filter by promotion state 'in progress'
+ Default: false
+ fractFilter: `BLUFOR` (enum[string], optional)
Field to filter by fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ Response 200 (application/json; charset=utf-8)
+ Attributes (array[PromotionPopulated])
### Create Promotion Proposal [POST /request/promotion]
Create a new proposal for a promotion, that needs to be approved by higher permission level user to take effect
**Permission: 1**
+ Request (application/json)
+ Attributes
+ userId: `5ab68d42f547ed304064e5f7` (string, required) - id of the army member to promote
+ oldRankLvl: 0 (number, required) - previous rank level
+ newRankLvl: 5 (number, required) - new rank level to apply
+ Response 201 (application/json; charset=utf-8)
+ Attributes (Promotion, fixed-type)
### Update Promotion Proposal [PATCH /request/promotion/{id}]
Update the promotion proposal
**Permission: 2**
+ Parameters
+ id: `5abf50d9861d950f157c4a0a` (string, required) - unique id of the promotion
+ Request (application/json)
+ Attributes
+ _id: `5abf50d9861d950f157c4a0a` (string, optional) - id of the promotion proposal
+ userId: `5ab68d42f547ed304064e5f7` (string, optional) - id of the army member to promote
+ proposer: `5abf5064861d950f157c4a09` (string, optional) - id of the proposing app user
+ oldRankLvl: 0 (number, optional) - previous rank level
+ newRankLvl: 5 (number, optional) - new rank level to apply
+ confirmed: 1 (number, optional) confirmation status of the promotion (0 - in progress, 1 - approved & applied, 2 - rejected) \nThis automatically applies the *newRankLvl*value as new rank level to the target user when confirmed = 1
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Promotion, fixed-type)

View File

@ -1,118 +0,0 @@
### List Ranks [GET /ranks{?q,fractFilter}]
List all ranks
+ Parameters
+ q: `Gefr` (string, optional) - filter string which filters for partial rank name
+ fractFilter: `BLUFOR` (enum[string], optional)
Field to filter by fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ Response 200 (application/json; charset=utf-8)
+ Attributes (array[Rank], fixed-type)
### Get Rank [GET /ranks/{id}]
Retrieve single rank data
+ Parameters
+ id: `5aba5504eadcce6332c6a775` (string, required) - unique id of the rank to fetch
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Rank, fixed-type)
### Create Rank [POST /ranks]
Create a new rank
**Permission: 2**
+ Request (multipart/form-data; boundary=---BOUNDARY)
-----BOUNDARY
Content-Disposition: form-data; name="name"
Content-Type: text/plain
Obergefreiter
-----BOUNDARY
Content-Disposition: form-data; name="fraction"
Content-Type: text/plain
BLUFOR
-----BOUNDARY
Content-Disposition: form-data; name="level"
Content-Type: text/plain
2
-----BOUNDARY
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
-----BOUNDARY--
+ Response 201 (application/json; charset=utf-8)
+ Attributes (Rank, fixed-type)
### Update Rank [PATCH /ranks/{id}]
Update rank, identified by its id
**Permission: 2**
+ Parameters
+ id: `5abeb23995cf43205225710b` (string, required) - unique id of the rank
+ Request (multipart/form-data; boundary=---BOUNDARY)
-----BOUNDARY
Content-Disposition: form-data; name="name"
Content-Type: text/plain
Stabsunteroffizier
-----BOUNDARY
Content-Disposition: form-data; name="fraction"
Content-Type: text/plain
BLUFOR
-----BOUNDARY
Content-Disposition: form-data; name="level"
Content-Type: text/plain
10
-----BOUNDARY
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
-----BOUNDARY--
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Rank, fixed-type)
### Delete Rank [DELETE /ranks/{id}]
Delete a rank
**Permission: 2**
+ Parameters
+ id: `5abeb1b995cf43205225710a` (string, required) - unique id of the rank
+ Response 204

View File

@ -1,112 +0,0 @@
### List Squads [GET /squads{?q,fractFilter}]
Get single army member information
+ Parameters
+ q: `alpha` (string, optional) - filter string which filters for partial squadname
+ fractFilter: `BLUFOR` (enum[string], optional)
Field to filter by fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ Response 200 (application/json; charset=utf-8)
+ Attributes (array[Squad], fixed-type)
### Get Squad [GET /squads/{id}]
Get single squad information
+ Parameters
+ id: `5aba54eaeadcce6332c6a774` (string, required) - unique id of the squad
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Squad, fixed-type)
### Create Squad [POST /squads]
Create a new squad
**Permission: 2**
+ Request (multipart/form-data; boundary=---BOUNDARY)
-----BOUNDARY
Content-Disposition: form-data; name="name"
Content-Type: text/plain
Delta
-----BOUNDARY
Content-Disposition: form-data; name="fraction"
Content-Type: text/plain
BLUFOR
-----BOUNDARY
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
-----BOUNDARY--
+ Response 201 (application/json; charset=utf-8)
+ Attributes (Squad, fixed-type)
### Update Squad [PATCH /squads/{id}]
Update squad, identified by its id
**Permission: 2**
+ Parameters
+ id: `5abe166f8b7488392a623f12` (string, required) - unique id of the squad
+ Request (multipart/form-data; boundary=---BOUNDARY)
-----BOUNDARY
Content-Disposition: form-data; name="name"
Content-Type: text/plain
test-CSAT
-----BOUNDARY
Content-Disposition: form-data; name="fraction"
Content-Type: text/plain
OPFOR
-----BOUNDARY
Content-Disposition: form-data; name="sortingNumber"
Content-Type: text/plain
3
-----BOUNDARY
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
-----BOUNDARY--
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Squad, fixed-type)
### Delete Squad [DELETE /squads/{id}]
Delete a squad
**Permission: 2**
+ Parameters
+ id: `5abe16f98b7488392a623f17` (string, required) - unique id of the squad
+ Response 204

View File

@ -1,96 +0,0 @@
### List Users [GET /users{?q,fractFilter,squadId,decorationId,limit,offset}]
Get single army member information
+ Parameters
+ q: `hardi` (string, optional) - filter string which filters for partial username
+ fractFilter: `BLUFOR` (enum[string], optional) - Field to filter by fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ squadId: `5aba54eaeadcce6332c6a774` (string, optional) - Field to filter by membership of certain squad
+ decorationId: `5abd3dff6e6a0334d95b8ba0` (string, optional) - Field to filter by ownership of certain decoration
+ limit: 20 (number, optional) - Maximum number of users to return
+ Default: Infinity
+ offset: 0 (number, optional) - Offset into result-set (useful for pagination)
+ Default: 0
+ Response 200 (application/json; charset=utf-8)
+ Headers
X-Total-Count: 1
+ Attributes (array[UserPopulated], fixed-type)
### Get User [GET /users/{id}]
Get single army member information
+ Parameters
+ id: `5ab68d42f547ed304064e5f7` (string, required) - unique id of army-member
+ Response 200 (application/json; charset=utf-8)
+ Attributes (UserPopulated, fixed-type)
### Create User [POST /users]
Create a new army member
**Permission: 2**
+ Request (application/json)
+ Attributes
+ username: `[GNC]Paolo` (string, required) - display name of the user
+ Response 201 (application/json; charset=utf-8)
+ Attributes (UserInclTimestamp, fixed-type)
### Update User [PUT /users/{id}]
Update an army member, identified by its id
**Permission: 2**
+ Parameters
+ id: `5abd4780396bc0487068be0e` (string, required) - unique id of army-member
+ Request (application/json)
+ Attributes
+ _id: `5abd4780396bc0487068be0e` (string, required) - unique id of army-member
+ username: `Paolo` (string, optional) - display name of the user
+ rankLvl: 22 (number, optional) - rank level representing the rank
+ squadId: `591470249e9fae286e008e31` (string, optional) - squadId of squad which army member is part of
+ Response 200 (application/json; charset=utf-8)
+ Attributes (User, fixed-type)
### Delete User [DELETE /users/{id}]
Delete an army member
**Permission: 2**
+ Parameters
+ id: `5abd4780396bc0487068be0e` (string, required) - unique id of army-member
+ Response 204

View File

@ -1,11 +0,0 @@
### Login - Generate Token [POST /authenticate]
Generate a token which can be used to make API requests.
+ Request Generate Token (application/json)
+ Attributes (Login)
+ Response 200 (application/json; charset=utf-8)
+ Attributes (LoginResponse)

View File

@ -1,13 +0,0 @@
### Sign up - Create account [POST /authenticate/signup]
Create a new application user account.
+ Request Create Account (application/json)
+ Attributes (Registration)
+ Response 201 (application/json; charset=utf-8)
+ Attributes
+ message: `User successfully created` (string, required)

View File

@ -1,54 +0,0 @@
## Location
The API can only be accessed via HTTPS in the cloud instance.
You can reach the API for the cloud instance at
```
https://cc.noarch.de/api/
```
Local installations can access the API using the respective domain with the path `/api/` appended.
So if your server is reachable at `https://cc.myserver.lan` the API endpoint is located at
```
# Note: `/api/` is appended to the locale installation server url
https://cc.myserver.lan/api/
```
## Authentication
Requests to most of the API endpoints have to be authenticated using an API token which is received after successfully providing your application credentials to the API.
Once acquired the token has to be provided in the ``X-Access-Token`` header of every request that requires authentication.
The following example illustrates how to provide the token when using `curl`
```
# via Authorization header
> curl -H "X-Access-Token: $CC_API_TOKEN" <API_RESOURCE_PATH_GOES_HERE>
```
## Permission Levels
The permission level is resolved by the API token you send, which resolves the specific app user information.
Endpoints that require a certain permission level to be accessed, mention the **minimum** required permission level in the description.
::: note
Permission Levels:
0 - User
1 - Squadlead
2 - High Command
3 - Maintainer
4 - Administrator
:::

View File

@ -1,10 +0,0 @@
## Timezones & Date Format
**Date Format**
In general the API returns and expects dates & times in the ISO 8601 format.
For example: `2017-02-27T17:05:33.155Z` for a time or `2017-02-27` for a date.
**Timezone**
The default timezone returned will be the UTC timezone

View File

@ -1,36 +0,0 @@
The running express server instance saves entity related files like squad logos and statistic logs on the file system.
In the original MEAN application they are served in the `/resource` path of the application server.
The following examples show where those files are located, to be accessed on the cloud instance.
## Images
**Decoration Image**
`https://cc.noarch.de/resource/decoration/{decorationId}.png`
**Rank Image**
`https://cc.noarch.de/resource/rank/{rankId}.png`
**Signature Image**
`https://cc.noarch.de/resource/signature/{userId}.png`
**Squad Logo**
`https://cc.noarch.de/resource/squad/{squadId}.png`
## Log-Files
**Sanitized Log File**
`https://cc.noarch.de/resource/logs/{warId}/clean.log`
**Original Uploaded Log File**
`https://cc.noarch.de/resource/logs/{warId}/war.log`

View File

@ -1,31 +0,0 @@
# Proposer (object)
Representation of an app user who proposed an awarding or a promotion
## Properties
+ _id: `5abf5064861d950f157c4a09` (string, required) - unique id of the app user
+ username: `hardiready` (string, required) - username of the app user
+ squad: `591470249e9fae286e308e41` (string, required) - squad id associated with the app user
# AppUser (Proposer)
An app user instance with populated squad
## Properties
+ activated: true (boolean, required) - account activation status
+ password: `$1s23$1$H7dl7RTFZUBIBNUZ213IIOUasdNEI571sMuzXmzi4` (string, required) - password hash value
+ permission: 1 (number, required) - permission level
+ secret: `I like tacos` (string, required) - secret used for account activation comparison
+ squad (Squad, required, nullable) - squad the app user is responsible for
+ timestamp: `2017-08-02T07:48:56.378Z` (string, required) - creation timestamp
+ updatedAt: `2017-08-02T08:07:20.929Z` (string, required) - version timestamp
+ __v: 3 (number, required) - version number
# UpdateAppUser (Proposer)
An app user instance for PATCH updating
## Properties
+ activated: true (boolean, optional) - account activation status
+ password: `$1s23$1$H7dl7RTFZUBIBNUZ213IIOUasdNEI571sMuzXmzi4` (string, optional) - password hash value
+ permission: 1 (number, optional) - permission level
+ secret: `I like tacos` (string, optional) - secret used for account activation comparison
+ squad: `5abe166f8b7488392a623f12` (string, optional) - id of squad the app user is responsible for

View File

@ -1,26 +0,0 @@
# Fraction (object)
Single fraction object
## Properties
+ squads (array[ArmySquad], required) - Array containing all squads of the fraction, sorted by 'sortingNumber'
+ memberCount: 1 (number, required) - sum of fraction members in all squads
# ArmySquad (object)
A single squad as appearing in the army overview
## Properties
+ _id: `59146e6aef2ad810623ed519` (string, required) - the unique id of the squad
+ name: `Führungsstab` (string, required) - the squad display name
+ members (array[ArmyMember], required) - List of participants being member in this squad
+ memberCount: 1 (number, required) - Number of members the squad has
# ArmyMember (object)
Unique member in a army squad
## Properties
+ _id: `5918d2ca574b0b16820a0b28` (string, required) - unique id of the army member
+ username: `Jagernaut` (string, required) - display name of the army member
+ rank: `General` (string, required) - display name of the rank which is assigned to this army member

View File

@ -1,29 +0,0 @@
# Login (object)
User related entity for creating a token using application credentials
## Properties
+ username: `testuser` (string, required) - username of the app-user
+ password: `password` (string, required) - password of the app-user
# LoginResponse (object)
Response object on successful token creation
## Properties
+ _id: `593d632772d35225232bcabb` (string, required) - the unique id of the app-user
+ username: `kallek` (string, required) - the username of the app-user
+ permission: 3 (number, required)- the permission level of the app-user (0 - user, 1 - sql, 2 - hl, 3 - maintainer, 4 - admin)
+ squad (Squad, required) - squad the app-user is member of
+ token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1OTNkNWUzZjcyZDM1MjI1MjIyYmNhYmEiLCJpYXQiOjE1MjEzOTQ5NDYsImV4cCI6MTUyMzgxNDE0Nn0.vOAwA5--qrx8BglhyGZWVYx7LeuRKOHH0NQXmHFNhIQ` (string,required) - generated access token
+ tokenExpireDate: `2018-04-15T17:42:25.050Z` (string,required) - expiration date of the token
# Registration (object)
User related entity for reflecting membership to another resource (groups, material databases)
## Properties
+ username: `kallek` (string, required) - the username of the app-user
+ password: `password` (string, required) - password of the app-user
+ secret: `random secret text` (string, required) - secret used for sign-up identification in connection with board usage

View File

@ -1,23 +0,0 @@
# Awarding (object)
Awarding associating a decoration to a user
## Properties
+ _id: `591ca81c1ee62711cfc19002` (string, required) - unique id of the awarding
+ confirmed: 1 (number, required) - status of the awarding request (0 - in progress, 1 - approved, 2 - rejected)
+ date: `2017-05-17T19:44:24.926Z` (string, required) - date when the awarding was requested
+ decorationId: `591c81981ee62711cfc18f4a` (string, required) - id of decoration that is given with the awarding
+ reason: `war dabei` (string, required) - reason for giving the awarding
+ proposer: 593d5e3f72d35225222bcaba (string, required) - id of app user who requested this awarding
+ timestamp: `2017-05-17T19:44:28.751Z` (string, required) - creation date
+ updatedAt: `"2017-05-17T19:44:28.751Z` (string, required) - version date
+ userId: `5918d2ca574b0b1d820a0b24` (string, required) - the unique id of the user who got this awarding
+ __v: 0 (number, required) - version number
# AwardingPopulated (Awarding)
Awarding with populated decoration, proposer and army-user
## Properties
+ proposer (Proposer, required) - app user who requested this awarding
+ decorationId (Decoration, required) - populated decoration object that is given with the awarding
+ userId (User, required) - populated user who gets this awarding

View File

@ -1,19 +0,0 @@
# Campaign (object)
Campaign entity
## Properties
+ _id:`5abd55ea9e30a76bfef747d6` (string, required) - unique id of the campaign
+ title: `Ein Kessel Buntes` (string, required) - display title of the campaign
+ timestamp: `2017-05-17T19:44:28.751Z` (string, required) - creation date
+ updatedAt: `"2017-05-17T19:44:28.751Z` (string, required) - version date
+ __v: 0 (number, required) - version number
#WarCampaign (object)
Cmpaign entity with attached War collection
## Properties
+ _id: `5abd55ea9e30a76bfef747d6` (string, required) - unique id of the campaign
+ title: `Ein Kessel Buntes` (string, required) - display title of the campaign
+ wars: WarWithPlayers (array[WarWithPlayers], required)

View File

@ -1,19 +0,0 @@
# Decoration (object)
A decoration representing a ribbon or medal
## Properties
+ _id: `591c81981ee62711cfc18f4a` (string, required) - unique id of the decoration
+ description: `Verliehen an alle Teilnehmer der ArmA3-Kampagne "Sturm über der Ägäis" im Jahr 2016` (string, required) - description expressing what the decoration should be given for
+ fraction: `GLOBAL` (string, required) - enum expressing the fraction in which the decoration is given
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ isMedal: false (boolean, required) - show if decoration is medal or ribbon
+ name: `OPT4 pt.1 - "Sturm über der Ägäis"` (string, required) - display name of the decoration
+ sortingNumber: 10 (number, required) - sorting number for lists
+ timestamp: `2017-05-17T17:00:08.684Z` (string, required) - creation timestamp
+ updatedAt: `2018-01-13T09:57:50.827Z` (string, required) - version timestamp
+ __v: 2 (number, required) - version number

View File

@ -1,91 +0,0 @@
# Log (object)
## Properties
+ _id: `` (string, required) - log entry id
+ war: `` (string, required) - warId
+ time: `` (string, required) - logging timestamp
# LogPoints (Log)
## Properties
+ ptBlufor: 2 (number, required) - standings for BLUFOR
+ ptOpfor: 4 (number, required) - standings for OPFOR
+ fraction: `OPFOR` (enum, required) - dominating fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `NONE`
#LogBudget (Log)
## Properties
+ oldBudget: 400000 (number, required) - budget before buy action
+ newBudget: 380000 (number, required) - budget after buy action
+ fraction: `BLUFOR` (enum, required) - buying fraction
+ Members
+ `BLUFOR`
+ `OPFOR`
#LogFlag (Log)
## Properties
+ player: `HardiReady` (string, required) - name of player who captured/secured flag
+ capture: true (boolean, required) - true if flag was captured, false if it was secured
+ flagFraction: `BLUFOR` (enum, required) - fraction who owns the flag
+ Members
+ `BLUFOR`
+ `OPFOR`
#LogKill (Log)
## Properties
+ shooter: `HardiReady` (string, required) - name of player who made the kill
+ target: `KalleK` (string, required) - name of player which got killed
+ friendlyFire: true (boolean, required) - true if it was a friendly fire kill, false if it was a normal kill
+ fraction: `BLUFOR` (enum, required) - fraction of shooter
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `NONE`
+ shooterVehicle: `FV-720 Mora` (string, optional) - vehicle in whiock the shooting player sat
+ targetVehicle: `Ifrit-GMG` (string, optional) - vehicle in which the target player sat
+ magazine: `30 mm APFSDS` (string, optional) - magazine name used to execute the kill
#LogRespawn (Log)
## Properties
+ player: `radical1976` (string, required) - name of the player who respawns
#LogRevive (Log)
## Properties
+ medic: `radical1976` (string, required) - name of the player who revives/stabilizes
+ patient: `HardiReady` (string, required) - name of the player who is revived/stabilized
+ stabilized: false (boolean, required) - false if it is a revive, true if it is stabilizing
+ fraction: `BLUFOR` (enum, required) - fraction of the medic
+ Members
+ `BLUFOR`
+ `OPFOR`
#LogTransport (Log)
## Properties
+ driver: `radical1976` (string, required) - name of the vehicle driver/pilot
+ passenger: `radical1976` (string, required) - name of the passenger being transported
+ distance: 2435 (number, required) - distance of transport in meters
+ fraction: `BLUFOR` (enum, required) - fraction of the driver
+ Members
+ `BLUFOR`
+ `OPFOR`
#LogVehicle (Log)
## Properties
+ shooter: `HardiReady` (string, required) - name of player who shot the vehicle
+ additionalShooter: `[GNC]Paolo`, `Dominik` (array[string], required) - additional crew members of shooter vehicle
+ target: `T-100` (string, required) - name of the vehicle
+ fraction: `BLUFOR` (enum, required) - fraction of the shooter
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `NONE`
+ vehicleClass: `LIGHT` (enum[string], required) - class of shot vehicle
+ Members
+ `LIGHT`
+ `HEAVY`
+ `AIR`
+ `BOAT`
+ `UNKNOWN`
+ shooterVehicle: `FV-720 Mora` (string, optional) - vehicle in whiock the shooting player sat
+ magazine: `30 mm APFSDS` (string, optional) - magazine name used to execute the kill

View File

@ -1,39 +0,0 @@
# BasicPlayer (object)
Basic player statistic information object
##Properties
+ name: `Jagernaut` (string, required) - the displayed username of the army member
+ fraction: `OPFOR` (string, required) - fraction of the player
+ kill: 5 (number, required) - sum of kills
+ friendlyFire: 0 (number, required) - sum of friendly fire kills
+ vehicleLight: 1 (number, required) - sum of light vehicle kills
+ vehicleHeavy: 1 (number, required) - sum of heavy vehicle kills
+ vehicleAir: 0 (number, required) - sum of air vehicle kills
+ death: 3 (number, required) - sum of deaths
+ respawn: 2 (number, required) - sum of respawns
+ flagTouch: 1 (number, required) - sum of flag captures
+ revive: 0 (number, required) - sum of revives
+ travelDistance: 16535 (number, optional) - sum of transport meters as passenger
+ driverDistance: 1250 (number, optional) - sum of transport meters as pilot/driver
# WarPlayer (BasicPlayer)
A player campaign statistics object
## Properties
+ _id: `5ab68d42f547ed304064e5f7` (string, required) - unique id of the army member
+ warId: `5abf65ae3fc5fa349ffd5ca3` (string, required) - war in which this player took part
+ steamUUID: 76561192214911200 (number, optional) - unique ID for STEAM platform account
+ performance: `5abf65ae3fc5fa349ffd5cs2` (string, optional) - id of corresponding performance log entry
+ timestamp: `2018-02-24T01:01:25.825Z` - the entity creation timestamp
+ updatedAt: `2018-02-24T01:01:25.825Z` - the version timestamp
+ __v: 0 (number, required) - the version number of the object
# HighscorePlayer (BasicPlayer)
A player object as returned for the highscore arrays
## Properties
+ warId: War (War, required) - war in which this player took part
+ num: 1

View File

@ -1,20 +0,0 @@
# Promotion (object)
Representation of a promotion request for a army member
## Properties
+ _id: `5as7d05dcb90ce4da68c4f5f` (string, required) - unique id of the promotion request
+ confirmed: 0 (number, required) - number representing status of the promotion (0 - in progress, 1 - approved, 2 - rejected)
+ newRankLvl: 14 (number, required)- new rank level that is requested in this promotion
+ oldRankLvl: 10 (number, required)- old rank level of the user
+ proposer: `5abf5064861d950f157c4a09` (string, required) - id of app user who requested the promotion
+ timestamp: `2018-03-25T18:54:21.609Z` (string, required) - creation timestamp
+ updatedAt: `2018-03-25T18:54:21.609Z` (string, required) - version timestamp
+ userId: `5ab68d42f547ed304064e5f7` (string, required) - id of army member the promotion is requested for
+ __v: 0 (number, required) - version number of promotion instance
# PromotionPopulated (object)
Promotion with populated proposer and army member
## Properties
+ proposer (Proposer, required) - app user who requested the promotion
+ userId (User, required) - populated user instance of user the promotion is requested for

View File

@ -1,16 +0,0 @@
# Rank (object)
A rank that can be applied to army members by level/fraction
## Properties
+ _id: `591469c9ef4a6810623ed4ea` (string, required) - the unique id of the rank
+ name: `Gefreiter` (string, required) - display name of the rank
+ fraction: `BLUFOR` (enum, required) - enum expressing the fraction in which the decoration is given
+ Members
+ `BLUFOR`
+ `OPFOR`
+ `GLOBAL`
+ level: 1 (number, required) - rank level
+ updatedAt: `2017-09-20T20:25:29.995Z` (string, required) - the version timestamp
+ timestamp: `2017-05-11T13:40:25.051Z` (string, required) - the creation timestamp
+ __v: 1 (number, required) - version number

Some files were not shown because too many files have changed in this diff Show More