| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
dist/
 | 
			
		||||
tmp/
 | 
			
		||||
etc/
 | 
			
		||||
api/apib/documentation.apib
 | 
			
		||||
server/apib/documentation.apib
 | 
			
		||||
 | 
			
		||||
# dependencies
 | 
			
		||||
node_modules
 | 
			
		||||
| 
						 | 
				
			
			@ -47,8 +47,8 @@ Thumbs.db
 | 
			
		|||
# Internal Data
 | 
			
		||||
public/
 | 
			
		||||
mongodb-data/
 | 
			
		||||
api/resource/
 | 
			
		||||
api/apib/dredd/data/tmp-resource
 | 
			
		||||
server/resource/
 | 
			
		||||
server/apib/dredd/data/tmp-resource
 | 
			
		||||
backup/
 | 
			
		||||
 | 
			
		||||
# System
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,7 +53,7 @@ To compile the Angular code and afterwards start the Express server with `nodemo
 | 
			
		|||
npm run dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Any changes on `api` code will trigger an automatic restart of the Express server.
 | 
			
		||||
Any changes on `server` code will trigger an automatic restart of the Express server.
 | 
			
		||||
 | 
			
		||||
Changes on `static` code can be submitted with
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +69,7 @@ _TODO_
 | 
			
		|||
## License Information
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Express API (`/api`)
 | 
			
		||||
### NodeJS Express Server (`/server`)
 | 
			
		||||
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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,161 +0,0 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
// modules
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const express = require('express');
 | 
			
		||||
const multer = require('multer');
 | 
			
		||||
const storage = multer.memoryStorage();
 | 
			
		||||
const upload = multer({storage: storage});
 | 
			
		||||
 | 
			
		||||
// 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;
 | 
			
		||||
const resourceLocation = require('../middleware/resource-location').resourceLocation().concat('/decoration/');
 | 
			
		||||
 | 
			
		||||
// Mongoose Model using mongoDB
 | 
			
		||||
const DecorationModel = require('../models/decoration');
 | 
			
		||||
const AwardingsModel = require('../models/awarding');
 | 
			
		||||
 | 
			
		||||
// util
 | 
			
		||||
const genericGetById = require('./_generic').genericGetById;
 | 
			
		||||
 | 
			
		||||
const decoration = new 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(resourceLocation + 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) => {
 | 
			
		||||
            return genericGetById(req, res, next, DecorationModel);
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          .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 = resourceLocation + req.params.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) => {
 | 
			
		||||
            const id = req.params.id;
 | 
			
		||||
            DecorationModel.findByIdAndRemove(id, (err, item) => {
 | 
			
		||||
              if (err) {
 | 
			
		||||
                err.status = codes.wrongrequest;
 | 
			
		||||
              } else if (!item) {
 | 
			
		||||
                err = new Error('item not found');
 | 
			
		||||
                err.status = codes.notfound;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              // deleted all awardings linked to this decoration
 | 
			
		||||
              AwardingsModel.find({decorationId: id}).remove().exec();
 | 
			
		||||
 | 
			
		||||
              // delete graphic
 | 
			
		||||
              fs.unlink(resourceLocation.concat(id).concat('.png'),
 | 
			
		||||
                (err) => {
 | 
			
		||||
                  // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
 | 
			
		||||
                  res.locals.processed = true;
 | 
			
		||||
                  next(err);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          .all(
 | 
			
		||||
            routerHandling.httpMethodNotAllowed
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
// this middleware function can be used, if you like or remove it
 | 
			
		||||
// 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;
 | 
			
		||||
							
								
								
									
										22
									
								
								package.json
								
								
								
								
							
							
						
						| 
						 | 
				
			
			@ -1,23 +1,23 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "opt-cc",
 | 
			
		||||
  "version": "1.8.5",
 | 
			
		||||
  "version": "1.8.6",
 | 
			
		||||
  "author": "Florian Hartwich <hardi@noarch.de>",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "npm run deploy-static-prod && npm start --prefix ./api",
 | 
			
		||||
    "dev": "npm run deploy-static && npm run dev --prefix ./api",
 | 
			
		||||
    "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 ../api/resource/ public/resource",
 | 
			
		||||
    "deploy-static:api-docs": "npm run api:docs --prefix=api",
 | 
			
		||||
    "postinstall": "npm install --prefix ./static && npm install --prefix  ./api",
 | 
			
		||||
    "lint": "npm run lint --prefix=api && npm run lint --prefix=static",
 | 
			
		||||
    "test": "npm test --prefix ./api",
 | 
			
		||||
    "e2e": "npm run deploy-static && concurrently \"npm run start-test --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 start-test --prefix ./api",
 | 
			
		||||
    "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 ./api"
 | 
			
		||||
    "test-api": "npm run api:test-docs --prefix ./server"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,8 +63,9 @@ Create a new decoration
 | 
			
		|||
        Content-Type: image/png
 | 
			
		||||
        Content-Transfer-Encoding: base64
 | 
			
		||||
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlMA
 | 
			
		||||
        QObYZgAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
 | 
			
		||||
        B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
 | 
			
		||||
        AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
 | 
			
		||||
        -----BOUNDARY--
 | 
			
		||||
 | 
			
		||||
+ Response 201 (application/json; charset=utf-8)
 | 
			
		||||
| 
						 | 
				
			
			@ -107,8 +108,9 @@ Update decoration, identified by its id
 | 
			
		|||
        Content-Type: image/png
 | 
			
		||||
        Content-Transfer-Encoding: base64
 | 
			
		||||
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlMA
 | 
			
		||||
        QObYZgAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
 | 
			
		||||
        B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
 | 
			
		||||
        AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
 | 
			
		||||
        -----BOUNDARY--
 | 
			
		||||
 | 
			
		||||
+ Response 200 (application/json; charset=utf-8)
 | 
			
		||||
| 
						 | 
				
			
			@ -57,8 +57,9 @@ Create a new rank
 | 
			
		|||
        Content-Type: image/png
 | 
			
		||||
        Content-Transfer-Encoding: base64
 | 
			
		||||
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlMA
 | 
			
		||||
        QObYZgAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
 | 
			
		||||
        B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
 | 
			
		||||
        AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
 | 
			
		||||
        -----BOUNDARY--
 | 
			
		||||
 | 
			
		||||
+ Response 201 (application/json; charset=utf-8)
 | 
			
		||||
| 
						 | 
				
			
			@ -96,8 +97,9 @@ Update rank, identified by its id
 | 
			
		|||
        Content-Type: image/png
 | 
			
		||||
        Content-Transfer-Encoding: base64
 | 
			
		||||
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlMA
 | 
			
		||||
        QObYZgAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
 | 
			
		||||
        B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
 | 
			
		||||
        AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
 | 
			
		||||
        -----BOUNDARY--
 | 
			
		||||
 | 
			
		||||
+ Response 200 (application/json; charset=utf-8)
 | 
			
		||||
| 
						 | 
				
			
			@ -51,8 +51,9 @@ Create a new squad
 | 
			
		|||
        Content-Type: image/png
 | 
			
		||||
        Content-Transfer-Encoding: base64
 | 
			
		||||
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlMA
 | 
			
		||||
        QObYZgAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
 | 
			
		||||
        B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
 | 
			
		||||
        AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
 | 
			
		||||
        -----BOUNDARY--
 | 
			
		||||
 | 
			
		||||
+ Response 201 (application/json; charset=utf-8)
 | 
			
		||||
| 
						 | 
				
			
			@ -90,8 +91,9 @@ Update squad, identified by its id
 | 
			
		|||
        Content-Type: image/png
 | 
			
		||||
        Content-Transfer-Encoding: base64
 | 
			
		||||
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlMA
 | 
			
		||||
        QObYZgAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=
 | 
			
		||||
        iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
 | 
			
		||||
        B3RJTUUH4wIDDQIBeZj+RQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
 | 
			
		||||
        AAAAFklEQVQI12NgZ2f///8/w////xkZGQEq5gYTeotA1AAAAABJRU5ErkJggg==
 | 
			
		||||
        -----BOUNDARY--
 | 
			
		||||
 | 
			
		||||
+ Response 200 (application/json; charset=utf-8)
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 95 B  | 
| 
		 After Width: | Height: | Size: 95 B  | 
| 
		 After Width: | Height: | Size: 160 B  | 
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
2018/03/20, 20:05:43 "[OPT] (Budget) LOG: 0:00:00 --- Startbudget: NATO 4.5355e+006 - CSAT 4.22125e+006"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:06:45 "[OPT] (Budget) LOG: 0:01:02 --- NATO alt: 4.5355e+006 - neu: 4.1955e+006 - Differenz: -340000"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:10:11 "[OPT] (Abschuss) LOG: 0:04:28 --- Saxe (WEST) von: Selbstverschulden."
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:10:11 "[OPT] (Abschuss) LOG: 0:04:28 --- Pumarang (WEST) von: Saxe (WEST)."
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:10:13 "[OPT] (Budget) LOG: 0:04:30 --- CSAT alt: 2.38425e+006 - neu: 2.32425e+006 - Differenz: -60000"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:15:54 "[OPT] (Punkte) LOG: 0:10:11 --- Kein Dominator (NATO 0 | CSAT 0)"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:17:51 "[OPT] (Abschuss) LOG: 0:12:08 --- patze (EAST) von: Wiesl (WEST)."
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:18:20 "[OPT] (Fahne) LOG: 0:12:37 --- CSAT Flagge erobert von Wiesl"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:18:38 "[OPT] (Abschuss) LOG: 0:12:55 --- Nicolas (WEST) von: Wiesl (WEST)."
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:18:59 "[OPT] (Punkte) LOG: 0:13:16 --- NATO +1 (NATO 1 | CSAT 0)"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:19:38 "[OPT] (Fahne) LOG: 0:13:56 --- CSAT Flagge gesichert von ALASTOR"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:22:18 "[OPT] (Abschuss) LOG: 0:16:35 --- Fahrzeug: Hunter-HMG (OPT_NATO) von: Murda]X[ (EAST)."
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:37:19 "[OPT] (Transport) LOG: 0:31:36 --- Dominik (WEST) wurde von Ponykloete (WEST) eingeflogen (8666.94 m)"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:41:27 "[OPT] (Revive) LOG: 0:35:44 --- Bodochecker (EAST) wurde von ALASTOR (EAST) stabilisiert."
 | 
			
		||||
 | 
			
		||||
2018/03/20, 20:41:35 "[OPT] (Revive) LOG: 0:35:52 --- Andi-de (WEST) wurde von Wiesl (WEST) wiederbelebt."
 | 
			
		||||
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Budget) LOG: 2:30:00 --- Endbudget: (NATO 1997000 | CSAT 512000)"
 | 
			
		||||
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Punkte) LOG: 2:30:00 --- Endpunktestand: (NATO 34 | CSAT 25)"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
Error: ENOENT: no such file or directory, open '
 | 
			
		||||
2018/03/20, 20:05:43 "[OPT] (Budget) LOG: 0:00:00 --- Startbudget: NATO 4.5355e+006 - CSAT 4.22125e+006"
 | 
			
		||||
2018/03/20, 20:06:45 "[OPT] (Budget) LOG: 0:01:02 --- NATO alt: 4.5355e+006 - neu: 4.1955e+006 - Differenz: -340000"
 | 
			
		||||
2018/03/20, 20:10:11 "[OPT] (Abschuss) LOG: 0:04:28 --- Saxe (WEST) von: Selbstverschulden."
 | 
			
		||||
2018/03/20, 20:10:11 "[OPT] (Abschuss) LOG: 0:04:28 --- Pumarang (WEST) von: Saxe (WEST)."
 | 
			
		||||
2018/03/20, 20:10:13 "[OPT] (Budget) LOG: 0:04:30 --- CSAT alt: 2.38425e+006 - neu: 2.32425e+006 - Differenz: -60000"
 | 
			
		||||
2018/03/20, 20:10:38 "[OPT] (Respawn) LOG: 0:04:55 --- Spieler: Pumarang - Kosten: 3000"
 | 
			
		||||
2018/03/20, 20:15:54 "[OPT] (Punkte) LOG: 0:10:11 --- Kein Dominator (NATO 0 | CSAT 0)"
 | 
			
		||||
2018/03/20, 20:17:51 "[OPT] (Abschuss) LOG: 0:12:08 --- patze (EAST) von: Wiesl (WEST)."
 | 
			
		||||
2018/03/20, 20:18:20 "[OPT] (Fahne) LOG: 0:12:37 --- CSAT Flagge erobert von Wiesl"
 | 
			
		||||
2018/03/20, 20:18:38 "[OPT] (Abschuss) LOG: 0:12:55 --- Nicolas (WEST) von: Wiesl (WEST)."
 | 
			
		||||
2018/03/20, 20:18:59 "[OPT] (Punkte) LOG: 0:13:16 --- NATO +1 (NATO 1 | CSAT 0)"
 | 
			
		||||
2018/03/20, 20:19:38 "[OPT] (Fahne) LOG: 0:13:56 --- CSAT Flagge gesichert von ALASTOR"
 | 
			
		||||
2018/03/20, 20:22:18 "[OPT] (Abschuss) LOG: 0:16:35 --- Fahrzeug: Hunter-HMG (OPT_NATO) von: Murda]X[ (EAST)."
 | 
			
		||||
2018/03/20, 20:37:19 "[OPT] (Transport) LOG: 0:31:36 --- Dominik (WEST) wurde von Ponykloete (WEST) eingeflogen (8666.94 m)"
 | 
			
		||||
2018/03/20, 20:41:27 "[OPT] (Revive) LOG: 0:35:44 --- Bodochecker (EAST) wurde von ALASTOR (EAST) stabilisiert."
 | 
			
		||||
2018/03/20, 20:41:35 "[OPT] (Revive) LOG: 0:35:52 --- Andi-de (WEST) wurde von Wiesl (WEST) wiederbelebt."
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Mission) LOG: 2:30:00 --- Missionzeit abgelaufen"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- Pumarang (WEST), PUID 76561198050321485"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- Mercurat (WEST), PUID 76561198278842491"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- KalleK (EAST), PUID 76561197977676036"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- MAPster (EAST), PUID 76561198009882133"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- LyrikEmu (WEST), PUID 76561198218910400"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- Philipp (EAST), PUID 76561198041792069"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- Wiesl (WEST), PUID 76561198059648090"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Fraktionsuebersicht) LOG: 2:30:00 --- Murda]X[ (EAST), PUID 76561197971121630"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Budget) LOG: 2:30:00 --- Endbudget: (NATO 1997000 | CSAT 512000)"
 | 
			
		||||
2018/03/20, 22:35:43 "[OPT] (Punkte) LOG: 2:30:00 --- Endpunktestand: (NATO 34 | CSAT 25)"
 | 
			
		||||
'
 | 
			
		||||
| 
		 After Width: | Height: | Size: 95 B  | 
| 
		 After Width: | Height: | Size: 95 B  | 
| 
		 After Width: | Height: | Size: 160 B  | 
| 
		 After Width: | Height: | Size: 15 KiB  | 
| 
		 After Width: | Height: | Size: 65 KiB  | 
| 
		 After Width: | Height: | Size: 95 B  | 
| 
		 After Width: | Height: | Size: 160 B  | 
| 
						 | 
				
			
			@ -3,6 +3,9 @@
 | 
			
		|||
// HTTP status codes by name
 | 
			
		||||
const codes = require('../routes/http-codes');
 | 
			
		||||
 | 
			
		||||
// library to check image dimensions from file buffer
 | 
			
		||||
const sizeOf = require('buffer-image-size');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * check if id has valid UUID format
 | 
			
		||||
 *
 | 
			
		||||
| 
						 | 
				
			
			@ -22,4 +25,14 @@ const idValidator = (req, res, next) => {
 | 
			
		|||
  next();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const imageDimensionValidator = (imageFileBuf, maxWidth, maxHeight) => {
 | 
			
		||||
  const dimensions = sizeOf(imageFileBuf);
 | 
			
		||||
  if (dimensions.width > maxWidth || dimensions.height > maxHeight) {
 | 
			
		||||
    let err = new Error(`Image exceeds maximum dimensions of ${maxWidth}px width and ${maxHeight}px height`);
 | 
			
		||||
    err.status = codes.wrongrequest;
 | 
			
		||||
    return err;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.idValidator = idValidator;
 | 
			
		||||
exports.imageDimensionValidator = imageDimensionValidator;
 | 
			
		||||