Properly indent all source files
This commit is contained in:
		@@ -9,10 +9,10 @@
 | 
			
		||||
 | 
			
		||||
/* =================== USAGE ===================
 | 
			
		||||
 | 
			
		||||
    import * as express from "express";
 | 
			
		||||
    let app = express();
 | 
			
		||||
   import * as express from "express";
 | 
			
		||||
   let app = express();
 | 
			
		||||
 | 
			
		||||
 =============================================== */
 | 
			
		||||
   =============================================== */
 | 
			
		||||
 | 
			
		||||
/// <reference types="express-serve-static-core" />
 | 
			
		||||
/// <reference types="serve-static" />
 | 
			
		||||
@@ -28,96 +28,96 @@ import * as qs from "qs";
 | 
			
		||||
declare function e(): core.Express;
 | 
			
		||||
 | 
			
		||||
declare namespace e {
 | 
			
		||||
    /**
 | 
			
		||||
     * This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on
 | 
			
		||||
     * body-parser.
 | 
			
		||||
     * @since 4.16.0
 | 
			
		||||
     */
 | 
			
		||||
    let json: typeof bodyParser.json;
 | 
			
		||||
	/**
 | 
			
		||||
	 * This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on
 | 
			
		||||
	 * body-parser.
 | 
			
		||||
	 * @since 4.16.0
 | 
			
		||||
	 */
 | 
			
		||||
	let json: typeof bodyParser.json;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is a built-in middleware function in Express. It parses incoming requests with Buffer payloads and is based 
 | 
			
		||||
     * on body-parser.
 | 
			
		||||
     * @since 4.17.0
 | 
			
		||||
     */
 | 
			
		||||
    let raw: typeof bodyParser.raw;
 | 
			
		||||
	/**
 | 
			
		||||
	 * This is a built-in middleware function in Express. It parses incoming requests with Buffer payloads and is based 
 | 
			
		||||
	 * on body-parser.
 | 
			
		||||
	 * @since 4.17.0
 | 
			
		||||
	 */
 | 
			
		||||
	let raw: typeof bodyParser.raw;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is a built-in middleware function in Express. It parses incoming requests with text payloads and is based on
 | 
			
		||||
     * body-parser.
 | 
			
		||||
     * @since 4.17.0
 | 
			
		||||
     */
 | 
			
		||||
    let text: typeof bodyParser.text;
 | 
			
		||||
	/**
 | 
			
		||||
	 * This is a built-in middleware function in Express. It parses incoming requests with text payloads and is based on
 | 
			
		||||
	 * body-parser.
 | 
			
		||||
	 * @since 4.17.0
 | 
			
		||||
	 */
 | 
			
		||||
	let text: typeof bodyParser.text;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * These are the exposed prototypes.
 | 
			
		||||
     */
 | 
			
		||||
    let application: Application;
 | 
			
		||||
    let request: Request;
 | 
			
		||||
    let response: Response;
 | 
			
		||||
	/**
 | 
			
		||||
	 * These are the exposed prototypes.
 | 
			
		||||
	 */
 | 
			
		||||
	let application: Application;
 | 
			
		||||
	let request: Request;
 | 
			
		||||
	let response: Response;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is a built-in middleware function in Express. It serves static files and is based on serve-static.
 | 
			
		||||
     */
 | 
			
		||||
    let static: typeof serveStatic;
 | 
			
		||||
	/**
 | 
			
		||||
	 * This is a built-in middleware function in Express. It serves static files and is based on serve-static.
 | 
			
		||||
	 */
 | 
			
		||||
	let static: typeof serveStatic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is 
 | 
			
		||||
     * based on body-parser.
 | 
			
		||||
     * @since 4.16.0
 | 
			
		||||
     */
 | 
			
		||||
    let urlencoded: typeof bodyParser.urlencoded;
 | 
			
		||||
	/**
 | 
			
		||||
	 * This is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is 
 | 
			
		||||
	 * based on body-parser.
 | 
			
		||||
	 * @since 4.16.0
 | 
			
		||||
	 */
 | 
			
		||||
	let urlencoded: typeof bodyParser.urlencoded;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is a built-in middleware function in Express. It parses incoming request query parameters.
 | 
			
		||||
     */
 | 
			
		||||
    export function query(options: qs.IParseOptions | typeof qs.parse): Handler;
 | 
			
		||||
	/**
 | 
			
		||||
	 * This is a built-in middleware function in Express. It parses incoming request query parameters.
 | 
			
		||||
	 */
 | 
			
		||||
	export function query(options: qs.IParseOptions | typeof qs.parse): Handler;
 | 
			
		||||
 | 
			
		||||
    export function Router(options?: RouterOptions): core.Router;
 | 
			
		||||
	export function Router(options?: RouterOptions): core.Router;
 | 
			
		||||
 | 
			
		||||
    interface RouterOptions {
 | 
			
		||||
        /**
 | 
			
		||||
         * Enable case sensitivity.
 | 
			
		||||
         */
 | 
			
		||||
        caseSensitive?: boolean;
 | 
			
		||||
	interface RouterOptions {
 | 
			
		||||
		/**
 | 
			
		||||
		 * Enable case sensitivity.
 | 
			
		||||
		 */
 | 
			
		||||
		caseSensitive?: boolean;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Preserve the req.params values from the parent router.
 | 
			
		||||
         * If the parent and the child have conflicting param names, the child’s value take precedence.
 | 
			
		||||
         *
 | 
			
		||||
         * @default false
 | 
			
		||||
         * @since 4.5.0
 | 
			
		||||
         */
 | 
			
		||||
        mergeParams?: boolean;
 | 
			
		||||
		/**
 | 
			
		||||
		 * Preserve the req.params values from the parent router.
 | 
			
		||||
		 * If the parent and the child have conflicting param names, the child’s value take precedence.
 | 
			
		||||
		 *
 | 
			
		||||
		 * @default false
 | 
			
		||||
		 * @since 4.5.0
 | 
			
		||||
		 */
 | 
			
		||||
		mergeParams?: boolean;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Enable strict routing.
 | 
			
		||||
         */
 | 
			
		||||
        strict?: boolean;
 | 
			
		||||
    }
 | 
			
		||||
		/**
 | 
			
		||||
		 * Enable strict routing.
 | 
			
		||||
		 */
 | 
			
		||||
		strict?: boolean;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    interface Application extends core.Application { }
 | 
			
		||||
    interface CookieOptions extends core.CookieOptions { }
 | 
			
		||||
    interface Errback extends core.Errback { }
 | 
			
		||||
    interface ErrorRequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any,
 | 
			
		||||
      ReqQuery = core.Query>
 | 
			
		||||
        extends core.ErrorRequestHandler<P, ResBody, ReqBody, ReqQuery> { }
 | 
			
		||||
    interface Express extends core.Express { }
 | 
			
		||||
    interface Handler extends core.Handler { }
 | 
			
		||||
    interface IRoute extends core.IRoute { }
 | 
			
		||||
    interface IRouter extends core.IRouter { }
 | 
			
		||||
    interface IRouterHandler<T> extends core.IRouterHandler<T> { }
 | 
			
		||||
    interface IRouterMatcher<T> extends core.IRouterMatcher<T> { }
 | 
			
		||||
    interface MediaType extends core.MediaType { }
 | 
			
		||||
    interface NextFunction extends core.NextFunction { }
 | 
			
		||||
    interface Request<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, 
 | 
			
		||||
      ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery> { }
 | 
			
		||||
    interface RequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, 
 | 
			
		||||
      ReqQuery = core.Query> extends core.RequestHandler<P, ResBody, ReqBody, ReqQuery> { }
 | 
			
		||||
    interface RequestParamHandler extends core.RequestParamHandler { }
 | 
			
		||||
    export interface Response<ResBody = any> extends core.Response<ResBody> { }
 | 
			
		||||
    interface Router extends core.Router { }
 | 
			
		||||
    interface Send extends core.Send { }
 | 
			
		||||
	interface Application extends core.Application { }
 | 
			
		||||
	interface CookieOptions extends core.CookieOptions { }
 | 
			
		||||
	interface Errback extends core.Errback { }
 | 
			
		||||
	interface ErrorRequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any,
 | 
			
		||||
			  ReqQuery = core.Query>
 | 
			
		||||
				  extends core.ErrorRequestHandler<P, ResBody, ReqBody, ReqQuery> { }
 | 
			
		||||
	interface Express extends core.Express { }
 | 
			
		||||
	interface Handler extends core.Handler { }
 | 
			
		||||
	interface IRoute extends core.IRoute { }
 | 
			
		||||
	interface IRouter extends core.IRouter { }
 | 
			
		||||
	interface IRouterHandler<T> extends core.IRouterHandler<T> { }
 | 
			
		||||
	interface IRouterMatcher<T> extends core.IRouterMatcher<T> { }
 | 
			
		||||
	interface MediaType extends core.MediaType { }
 | 
			
		||||
	interface NextFunction extends core.NextFunction { }
 | 
			
		||||
	interface Request<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, 
 | 
			
		||||
			  ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery> { }
 | 
			
		||||
	interface RequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, 
 | 
			
		||||
			  ReqQuery = core.Query> extends core.RequestHandler<P, ResBody, ReqBody, ReqQuery> { }
 | 
			
		||||
	interface RequestParamHandler extends core.RequestParamHandler { }
 | 
			
		||||
	export interface Response<ResBody = any> extends core.Response<ResBody> { }
 | 
			
		||||
	interface Router extends core.Router { }
 | 
			
		||||
	interface Send extends core.Send { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export = e;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,111 +9,111 @@ import globals from '../globals';
 | 
			
		||||
// req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'}
 | 
			
		||||
 | 
			
		||||
module.exports = async (req, res, next) => {
 | 
			
		||||
  let givenMethod = '';  // authorization method given by client, basic taken preferred
 | 
			
		||||
  let user = {name: '', level: '', id: '', location: '', models: []};               // user object
 | 
			
		||||
	let givenMethod = '';  // authorization method given by client, basic taken preferred
 | 
			
		||||
	let user = {name: '', level: '', id: '', location: '', models: []};               // user object
 | 
			
		||||
 | 
			
		||||
  // test authentications
 | 
			
		||||
  const userBasic = await basic(req, next);
 | 
			
		||||
	// test authentications
 | 
			
		||||
	const userBasic = await basic(req, next);
 | 
			
		||||
 | 
			
		||||
  if (userBasic) {  // basic available
 | 
			
		||||
    givenMethod = 'basic';
 | 
			
		||||
    user = userBasic;
 | 
			
		||||
  }
 | 
			
		||||
  else {  // if basic not available, test key
 | 
			
		||||
    const userKey = await key(req, next);
 | 
			
		||||
    if (userKey) {
 | 
			
		||||
      givenMethod = 'key';
 | 
			
		||||
      user = userKey;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	if (userBasic) {  // basic available
 | 
			
		||||
		givenMethod = 'basic';
 | 
			
		||||
		user = userBasic;
 | 
			
		||||
	}
 | 
			
		||||
	else {  // if basic not available, test key
 | 
			
		||||
		const userKey = await key(req, next);
 | 
			
		||||
	if (userKey) {
 | 
			
		||||
		givenMethod = 'key';
 | 
			
		||||
		user = userKey;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  req.auth = (res, levels, method = 'all') => {
 | 
			
		||||
    if (givenMethod === method || (method === 'all' && givenMethod !== '')) {  // method is available
 | 
			
		||||
      if (levels.indexOf(user.level) > -1) {  // level is available
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(401).json({status: 'Unauthorized'});
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	req.auth = (res, levels, method = 'all') => {
 | 
			
		||||
		if (givenMethod === method || (method === 'all' && givenMethod !== '')) {  // method is available
 | 
			
		||||
			if (levels.indexOf(user.level) > -1) {  // level is available
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(401).json({status: 'Unauthorized'});
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  req.authDetails = {
 | 
			
		||||
    method: givenMethod,
 | 
			
		||||
    username: user.name,
 | 
			
		||||
    level: user.level,
 | 
			
		||||
    id: user.id,
 | 
			
		||||
    location: user.location,
 | 
			
		||||
    models: user.models
 | 
			
		||||
  };
 | 
			
		||||
	req.authDetails = {
 | 
			
		||||
		method: givenMethod,
 | 
			
		||||
		username: user.name,
 | 
			
		||||
		level: user.level,
 | 
			
		||||
		id: user.id,
 | 
			
		||||
		location: user.location,
 | 
			
		||||
		models: user.models
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  next();
 | 
			
		||||
	next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function basic (req, next): any {  // checks basic auth and returns changed user object
 | 
			
		||||
  return new Promise(resolve => {
 | 
			
		||||
    const auth = basicAuth(req);
 | 
			
		||||
    if (auth !== undefined) {  // basic auth available
 | 
			
		||||
      UserModel.find({name: auth.name, status: globals.status.new}).lean().exec( (err, data: any) => {  // find user
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        if (data.length === 1) {  // one user found
 | 
			
		||||
          bcrypt.compare(auth.pass, data[0].pass, (err, res) => {  // check password
 | 
			
		||||
            if (err) return next(err);
 | 
			
		||||
            if (res === true) {  // password correct
 | 
			
		||||
              resolve({
 | 
			
		||||
                level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
 | 
			
		||||
                name: data[0].name,
 | 
			
		||||
                id: data[0]._id.toString(),
 | 
			
		||||
                location: data[0].location,
 | 
			
		||||
                models: data[0].models
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              resolve(null);
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          resolve(null);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      resolve(null);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
	return new Promise(resolve => {
 | 
			
		||||
		const auth = basicAuth(req);
 | 
			
		||||
		if (auth !== undefined) {  // basic auth available
 | 
			
		||||
			UserModel.find({name: auth.name, status: globals.status.new}).lean().exec( (err, data: any) => {  // find user
 | 
			
		||||
				if (err) return next(err);
 | 
			
		||||
				if (data.length === 1) {  // one user found
 | 
			
		||||
					bcrypt.compare(auth.pass, data[0].pass, (err, res) => {  // check password
 | 
			
		||||
						if (err) return next(err);
 | 
			
		||||
						if (res === true) {  // password correct
 | 
			
		||||
							resolve({
 | 
			
		||||
								level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
 | 
			
		||||
								name: data[0].name,
 | 
			
		||||
								id: data[0]._id.toString(),
 | 
			
		||||
								location: data[0].location,
 | 
			
		||||
								models: data[0].models
 | 
			
		||||
							});
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
							resolve(null);
 | 
			
		||||
						}
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					resolve(null);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			resolve(null);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function key (req, next): any {  // checks API key and returns changed user object
 | 
			
		||||
  return new Promise(resolve => {
 | 
			
		||||
    if (req.query.key !== undefined) {  // key available
 | 
			
		||||
      UserModel.find({key: req.query.key, status: globals.status.new}).lean().exec( (err, data: any) => {  // find user
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        if (data.length === 1) {  // one user found
 | 
			
		||||
          resolve({
 | 
			
		||||
            level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
 | 
			
		||||
            name: data[0].name,
 | 
			
		||||
            id: data[0]._id.toString(),
 | 
			
		||||
            location: data[0].location,
 | 
			
		||||
            models: data[0].models
 | 
			
		||||
          });
 | 
			
		||||
          if (!/^\/api/m.test(req.url)){
 | 
			
		||||
            delete req.query.key;  // delete query parameter to avoid interference with later validation
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          resolve(null);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      resolve(null);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
	return new Promise(resolve => {
 | 
			
		||||
		if (req.query.key !== undefined) {  // key available
 | 
			
		||||
			UserModel.find({key: req.query.key, status: globals.status.new}).lean().exec( (err, data: any) => {  // find user
 | 
			
		||||
				if (err) return next(err);
 | 
			
		||||
				if (data.length === 1) {  // one user found
 | 
			
		||||
					resolve({
 | 
			
		||||
						level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
 | 
			
		||||
						name: data[0].name,
 | 
			
		||||
						id: data[0]._id.toString(),
 | 
			
		||||
						location: data[0].location,
 | 
			
		||||
						models: data[0].models
 | 
			
		||||
					});
 | 
			
		||||
					if (!/^\/api/m.test(req.url)){
 | 
			
		||||
						delete req.query.key;  // delete query parameter to avoid interference with later validation
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					resolve(null);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			resolve(null);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@ import {parseAsync} from 'json2csv';
 | 
			
		||||
import flatten from './flatten';
 | 
			
		||||
 | 
			
		||||
export default function csv(input: any[], f: (err, data) => void) {  // parse JSON to CSV
 | 
			
		||||
  parseAsync(input.map(e => flatten(e)), {includeEmptyRows: true})
 | 
			
		||||
    .then(csv => f(null, csv))
 | 
			
		||||
    .catch(err => f(err, null));
 | 
			
		||||
	parseAsync(input.map(e => flatten(e)), {includeEmptyRows: true})
 | 
			
		||||
	.then(csv => f(null, csv))
 | 
			
		||||
	.catch(err => f(err, null));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,42 +1,42 @@
 | 
			
		||||
import globals from '../globals';
 | 
			
		||||
 | 
			
		||||
export default function flatten (data, keepArray = false) {  // flatten object: {a: {b: true}} -> {a.b: true}
 | 
			
		||||
  const result = {};
 | 
			
		||||
  function recurse (cur, prop) {
 | 
			
		||||
    if (Object(cur) !== cur || Object.keys(cur).length === 0) {  // simple value
 | 
			
		||||
      result[prop] = cur;
 | 
			
		||||
    }
 | 
			
		||||
    else if (prop === `${globals.spectrum.spectrum}.${globals.spectrum.dpt}`) {  // convert spectrum for ML
 | 
			
		||||
      result[prop + '.labels'] = cur.map(e => parseFloat(e[0]));
 | 
			
		||||
      result[prop + '.values'] = cur.map(e => parseFloat(e[1]));
 | 
			
		||||
    }
 | 
			
		||||
    else if (Array.isArray(cur)) {
 | 
			
		||||
      if (keepArray) {
 | 
			
		||||
        result[prop] = cur;
 | 
			
		||||
      }
 | 
			
		||||
      else {  // array to string
 | 
			
		||||
        if (cur.length && (Object(cur[0]) !== cur || Object.keys(cur[0]).length === 0)) {  // array of non-objects
 | 
			
		||||
          result[prop] = '[' + cur.join(', ') + ']';
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          let l = 0;
 | 
			
		||||
          for(let i = 0, l = cur.length; i < l; i++)
 | 
			
		||||
            recurse(cur[i], prop + "[" + i + "]");
 | 
			
		||||
          if (l == 0)
 | 
			
		||||
            result[prop] = [];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {  // object
 | 
			
		||||
      let isEmpty = true;
 | 
			
		||||
      for (let p in cur) {
 | 
			
		||||
        isEmpty = false;
 | 
			
		||||
        recurse(cur[p], prop ? prop+"."+p : p);
 | 
			
		||||
      }
 | 
			
		||||
      if (isEmpty && prop)
 | 
			
		||||
        result[prop] = {};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  recurse(data, '');
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
	const result = {};
 | 
			
		||||
	function recurse (cur, prop) {
 | 
			
		||||
		if (Object(cur) !== cur || Object.keys(cur).length === 0) {  // simple value
 | 
			
		||||
			result[prop] = cur;
 | 
			
		||||
		}
 | 
			
		||||
		else if (prop === `${globals.spectrum.spectrum}.${globals.spectrum.dpt}`) {  // convert spectrum for ML
 | 
			
		||||
			result[prop + '.labels'] = cur.map(e => parseFloat(e[0]));
 | 
			
		||||
		result[prop + '.values'] = cur.map(e => parseFloat(e[1]));
 | 
			
		||||
		}
 | 
			
		||||
		else if (Array.isArray(cur)) {
 | 
			
		||||
			if (keepArray) {
 | 
			
		||||
				result[prop] = cur;
 | 
			
		||||
			}
 | 
			
		||||
			else {  // array to string
 | 
			
		||||
				if (cur.length && (Object(cur[0]) !== cur || Object.keys(cur[0]).length === 0)) {  // array of non-objects
 | 
			
		||||
					result[prop] = '[' + cur.join(', ') + ']';
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					let l = 0;
 | 
			
		||||
					for(let i = 0, l = cur.length; i < l; i++)
 | 
			
		||||
					recurse(cur[i], prop + "[" + i + "]");
 | 
			
		||||
					if (l == 0)
 | 
			
		||||
						result[prop] = [];
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {  // object
 | 
			
		||||
			let isEmpty = true;
 | 
			
		||||
			for (let p in cur) {
 | 
			
		||||
				isEmpty = false;
 | 
			
		||||
				recurse(cur[p], prop ? prop+"."+p : p);
 | 
			
		||||
			}
 | 
			
		||||
			if (isEmpty && prop)
 | 
			
		||||
				result[prop] = {};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	recurse(data, '');
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,86 +4,86 @@ import axios from 'axios';
 | 
			
		||||
 | 
			
		||||
export default class Mail{
 | 
			
		||||
 | 
			
		||||
  static readonly address = 'definma@bosch-iot.com';  // email address
 | 
			
		||||
  static uri: string;                                 // mail API URI
 | 
			
		||||
  static auth = {username: '', password: ''};         // mail API credentials
 | 
			
		||||
  static mailPass: string;                            // mail API generates password
 | 
			
		||||
	static readonly address = 'definma@bosch-iot.com';  // email address
 | 
			
		||||
	static uri: string;                                 // mail API URI
 | 
			
		||||
	static auth = {username: '', password: ''};         // mail API credentials
 | 
			
		||||
	static mailPass: string;                            // mail API generates password
 | 
			
		||||
 | 
			
		||||
  static init() {
 | 
			
		||||
    if (process.env.NODE_ENV === 'production') {  // only send mails in production
 | 
			
		||||
      this.mailPass = Array(64).fill(0).map(() => Math.floor(Math.random() * 10)).join('');
 | 
			
		||||
      this.uri = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.uri;
 | 
			
		||||
      this.auth.username = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.username;
 | 
			
		||||
      this.auth.password = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.password;
 | 
			
		||||
      axios({  // get registered mail addresses
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: this.uri + '/management/userDomainMapping',
 | 
			
		||||
        auth: this.auth
 | 
			
		||||
      }).then(res => {
 | 
			
		||||
        return new Promise(async (resolve, reject) => {
 | 
			
		||||
          try {
 | 
			
		||||
            if (res.data.addresses.indexOf(this.address) < 0) {  // mail address not registered
 | 
			
		||||
              if (res.data.addresses.length) {  // delete wrong registered mail address
 | 
			
		||||
                await axios({
 | 
			
		||||
                  method: 'delete',
 | 
			
		||||
                  url: this.uri + '/management/mailAddresses/' + res.data.addresses[0],
 | 
			
		||||
                  auth: this.auth
 | 
			
		||||
                });
 | 
			
		||||
              }
 | 
			
		||||
              await axios({  // register right mail address
 | 
			
		||||
                method: 'post',
 | 
			
		||||
                url: this.uri + '/management/mailAddresses/' + this.address,
 | 
			
		||||
                auth: this.auth
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
            resolve();
 | 
			
		||||
          }
 | 
			
		||||
          catch (e) {
 | 
			
		||||
            reject(e);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }).then(() => {
 | 
			
		||||
        return axios({  // set new mail password
 | 
			
		||||
          method: 'put',
 | 
			
		||||
          url: this.uri + '/management/mailAddresses/' + this.address + '/password/' + this.mailPass,
 | 
			
		||||
          auth: this.auth
 | 
			
		||||
        });
 | 
			
		||||
      }).then(() => {  // init done successfully
 | 
			
		||||
        console.info('Mail service established successfully');
 | 
			
		||||
      }).catch(err => {  // somewhere an error occurred
 | 
			
		||||
        console.error(`Mail init error: ${err.request.method} ${err.request.path}: ${err.response.status}`,
 | 
			
		||||
          err.response.data);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	static init() {
 | 
			
		||||
		if (process.env.NODE_ENV === 'production') {  // only send mails in production
 | 
			
		||||
			this.mailPass = Array(64).fill(0).map(() => Math.floor(Math.random() * 10)).join('');
 | 
			
		||||
			this.uri = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.uri;
 | 
			
		||||
			this.auth.username = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.username;
 | 
			
		||||
			this.auth.password = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.password;
 | 
			
		||||
			axios({  // get registered mail addresses
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: this.uri + '/management/userDomainMapping',
 | 
			
		||||
				auth: this.auth
 | 
			
		||||
			}).then(res => {
 | 
			
		||||
				return new Promise(async (resolve, reject) => {
 | 
			
		||||
					try {
 | 
			
		||||
						if (res.data.addresses.indexOf(this.address) < 0) {  // mail address not registered
 | 
			
		||||
							if (res.data.addresses.length) {  // delete wrong registered mail address
 | 
			
		||||
								await axios({
 | 
			
		||||
									method: 'delete',
 | 
			
		||||
									url: this.uri + '/management/mailAddresses/' + res.data.addresses[0],
 | 
			
		||||
									auth: this.auth
 | 
			
		||||
								});
 | 
			
		||||
							}
 | 
			
		||||
							await axios({  // register right mail address
 | 
			
		||||
								method: 'post',
 | 
			
		||||
								url: this.uri + '/management/mailAddresses/' + this.address,
 | 
			
		||||
								auth: this.auth
 | 
			
		||||
							});
 | 
			
		||||
						}
 | 
			
		||||
						resolve();
 | 
			
		||||
					}
 | 
			
		||||
					catch (e) {
 | 
			
		||||
						reject(e);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				return axios({  // set new mail password
 | 
			
		||||
					method: 'put',
 | 
			
		||||
					url: this.uri + '/management/mailAddresses/' + this.address + '/password/' + this.mailPass,
 | 
			
		||||
					auth: this.auth
 | 
			
		||||
				});
 | 
			
		||||
			}).then(() => {  // init done successfully
 | 
			
		||||
				console.info('Mail service established successfully');
 | 
			
		||||
			}).catch(err => {  // somewhere an error occurred
 | 
			
		||||
				console.error(`Mail init error: ${err.request.method} ${err.request.path}: ${err.response.status}`,
 | 
			
		||||
							  err.response.data);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static send (mailAddress, subject, content, f: (x?) => void = () => {}) {  // callback executed empty or with error
 | 
			
		||||
    if (process.env.NODE_ENV === 'production') {  // only send mails in production
 | 
			
		||||
      axios({
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: this.uri + '/email',
 | 
			
		||||
        auth: this.auth,
 | 
			
		||||
        data: {
 | 
			
		||||
          recipients: [{to: mailAddress}],
 | 
			
		||||
          subject: {content: subject},
 | 
			
		||||
          body: {
 | 
			
		||||
            content: content,
 | 
			
		||||
            contentType: "text/html"
 | 
			
		||||
          },
 | 
			
		||||
          from: {
 | 
			
		||||
            eMail: this.address,
 | 
			
		||||
            password: this.mailPass
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }).then(() => {
 | 
			
		||||
        f();
 | 
			
		||||
      }).catch((err) => {
 | 
			
		||||
        f(err);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else {  // dev dummy replacement
 | 
			
		||||
      console.info('Sending mail to ' + mailAddress + ':  -- ' + subject + ' -- ' + content);
 | 
			
		||||
      f();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	static send (mailAddress, subject, content, f: (x?) => void = () => {}) {  // callback executed empty or with error
 | 
			
		||||
		if (process.env.NODE_ENV === 'production') {  // only send mails in production
 | 
			
		||||
			axios({
 | 
			
		||||
				method: 'post',
 | 
			
		||||
				url: this.uri + '/email',
 | 
			
		||||
				auth: this.auth,
 | 
			
		||||
				data: {
 | 
			
		||||
					recipients: [{to: mailAddress}],
 | 
			
		||||
					subject: {content: subject},
 | 
			
		||||
					body: {
 | 
			
		||||
						content: content,
 | 
			
		||||
						contentType: "text/html"
 | 
			
		||||
					},
 | 
			
		||||
					from: {
 | 
			
		||||
						eMail: this.address,
 | 
			
		||||
						password: this.mailPass
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				f();
 | 
			
		||||
			}).catch((err) => {
 | 
			
		||||
				f(err);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		else {  // dev dummy replacement
 | 
			
		||||
			console.info('Sending mail to ' + mailAddress + ':  -- ' + subject + ' -- ' + content);
 | 
			
		||||
			f();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const ChangelogSchema = new mongoose.Schema({
 | 
			
		||||
  action: String,
 | 
			
		||||
  collection_name: String,
 | 
			
		||||
  conditions: mongoose.Schema.Types.Mixed,
 | 
			
		||||
  data: Object,
 | 
			
		||||
  user_id: mongoose.Schema.Types.ObjectId
 | 
			
		||||
	action: String,
 | 
			
		||||
	collection_name: String,
 | 
			
		||||
	conditions: mongoose.Schema.Types.Mixed,
 | 
			
		||||
	data: Object,
 | 
			
		||||
	user_id: mongoose.Schema.Types.ObjectId
 | 
			
		||||
}, {minimize: false, strict: false});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('changelog', ChangelogSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('changelog', ChangelogSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,19 +2,19 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const ConditionTemplateSchema = new mongoose.Schema({
 | 
			
		||||
  first_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
  name: String,
 | 
			
		||||
  version: Number,
 | 
			
		||||
  parameters: [new mongoose.Schema({
 | 
			
		||||
    name: String,
 | 
			
		||||
    range: mongoose.Schema.Types.Mixed
 | 
			
		||||
  } ,{ _id : false })]
 | 
			
		||||
	first_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
	name: String,
 | 
			
		||||
	version: Number,
 | 
			
		||||
	parameters: [new mongoose.Schema({
 | 
			
		||||
		name: String,
 | 
			
		||||
		range: mongoose.Schema.Types.Mixed
 | 
			
		||||
	} ,{ _id : false })]
 | 
			
		||||
}, {minimize: false});  // to allow empty objects
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
ConditionTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('condition_template', ConditionTemplateSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('condition_template', ConditionTemplateSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,15 +2,15 @@ import db from '../db';
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const HelpSchema = new mongoose.Schema({
 | 
			
		||||
  key: {type: String, index: {unique: true}},
 | 
			
		||||
  level: String,
 | 
			
		||||
  text: String
 | 
			
		||||
	key: {type: String, index: {unique: true}},
 | 
			
		||||
	level: String,
 | 
			
		||||
	text: String
 | 
			
		||||
}, {minimize: false});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
HelpSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('help', HelpSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('help', HelpSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,20 @@ import MaterialGroupsModel from '../models/material_groups';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const MaterialSchema = new mongoose.Schema({
 | 
			
		||||
  name: {type: String, index: {unique: true}},
 | 
			
		||||
  supplier_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialSupplierModel},
 | 
			
		||||
  group_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialGroupsModel},
 | 
			
		||||
  properties: mongoose.Schema.Types.Mixed,
 | 
			
		||||
  numbers: [String],
 | 
			
		||||
  status: String
 | 
			
		||||
	name: {type: String, index: {unique: true}},
 | 
			
		||||
	supplier_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialSupplierModel},
 | 
			
		||||
	group_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialGroupsModel},
 | 
			
		||||
	properties: mongoose.Schema.Types.Mixed,
 | 
			
		||||
	numbers: [String],
 | 
			
		||||
	status: String
 | 
			
		||||
}, {minimize: false});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
MaterialSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
MaterialSchema.index({supplier_id: 1});
 | 
			
		||||
MaterialSchema.index({group_id: 1});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material', MaterialSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material', MaterialSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,13 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const MaterialGroupsSchema = new mongoose.Schema({
 | 
			
		||||
  name: {type: String, index: {unique: true}}
 | 
			
		||||
	name: {type: String, index: {unique: true}}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
MaterialGroupsSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material_groups', MaterialGroupsSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material_groups', MaterialGroupsSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,13 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const MaterialSuppliersSchema = new mongoose.Schema({
 | 
			
		||||
  name: {type: String, index: {unique: true}}
 | 
			
		||||
	name: {type: String, index: {unique: true}}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
MaterialSuppliersSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material_suppliers', MaterialSuppliersSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material_suppliers', MaterialSuppliersSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,19 +2,19 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const MaterialTemplateSchema = new mongoose.Schema({
 | 
			
		||||
  first_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
  name: String,
 | 
			
		||||
  version: Number,
 | 
			
		||||
  parameters: [new mongoose.Schema({
 | 
			
		||||
    name: String,
 | 
			
		||||
    range: mongoose.Schema.Types.Mixed
 | 
			
		||||
  } ,{ _id : false })]
 | 
			
		||||
	first_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
	name: String,
 | 
			
		||||
	version: Number,
 | 
			
		||||
	parameters: [new mongoose.Schema({
 | 
			
		||||
		name: String,
 | 
			
		||||
		range: mongoose.Schema.Types.Mixed
 | 
			
		||||
	} ,{ _id : false })]
 | 
			
		||||
}, {minimize: false});  // to allow empty objects
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
MaterialTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material_template', MaterialTemplateSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('material_template', MaterialTemplateSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,18 +6,18 @@ import db from '../db';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const MeasurementSchema = new mongoose.Schema({
 | 
			
		||||
  sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel},
 | 
			
		||||
  values: mongoose.Schema.Types.Mixed,
 | 
			
		||||
  measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel},
 | 
			
		||||
  status: String
 | 
			
		||||
	sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel},
 | 
			
		||||
	values: mongoose.Schema.Types.Mixed,
 | 
			
		||||
	measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel},
 | 
			
		||||
	status: String
 | 
			
		||||
}, {minimize: false});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
MeasurementSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
MeasurementSchema.index({sample_id: 1});
 | 
			
		||||
MeasurementSchema.index({measurement_template: 1});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('measurement', MeasurementSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('measurement', MeasurementSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,19 +2,19 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const MeasurementTemplateSchema = new mongoose.Schema({
 | 
			
		||||
  first_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
  name: String,
 | 
			
		||||
  version: Number,
 | 
			
		||||
  parameters: [new mongoose.Schema({
 | 
			
		||||
    name: String,
 | 
			
		||||
    range: mongoose.Schema.Types.Mixed
 | 
			
		||||
  } ,{ _id : false })]
 | 
			
		||||
	first_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
	name: String,
 | 
			
		||||
	version: Number,
 | 
			
		||||
	parameters: [new mongoose.Schema({
 | 
			
		||||
		name: String,
 | 
			
		||||
		range: mongoose.Schema.Types.Mixed
 | 
			
		||||
	} ,{ _id : false })]
 | 
			
		||||
}, {minimize: false});  // to allow empty objects
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
MeasurementTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('measurement_template', MeasurementTemplateSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('measurement_template', MeasurementTemplateSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,19 +2,19 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const ModelSchema = new mongoose.Schema({
 | 
			
		||||
  group: {type: String, index: {unique: true}},
 | 
			
		||||
  models: [new mongoose.Schema({
 | 
			
		||||
    name: String,
 | 
			
		||||
    url: String,
 | 
			
		||||
    label: String
 | 
			
		||||
  } ,{ _id : true })]
 | 
			
		||||
	group: {type: String, index: {unique: true}},
 | 
			
		||||
	models: [new mongoose.Schema({
 | 
			
		||||
		name: String,
 | 
			
		||||
		url: String,
 | 
			
		||||
		label: String
 | 
			
		||||
	} ,{ _id : true })]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
ModelSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
ModelSchema.index({group: 1});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('model', ModelSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('model', ModelSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const ModelFileSchema = new mongoose.Schema({
 | 
			
		||||
  name: {type: String, index: {unique: true}},
 | 
			
		||||
  data: Buffer
 | 
			
		||||
	name: {type: String, index: {unique: true}},
 | 
			
		||||
	data: Buffer
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('model_file', ModelFileSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('model_file', ModelFileSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,18 +2,18 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const NoteSchema = new mongoose.Schema({
 | 
			
		||||
  comment: String,
 | 
			
		||||
  sample_references: [{
 | 
			
		||||
    sample_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
    relation: String
 | 
			
		||||
  }],
 | 
			
		||||
  custom_fields: mongoose.Schema.Types.Mixed
 | 
			
		||||
	comment: String,
 | 
			
		||||
	sample_references: [{
 | 
			
		||||
		sample_id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
		relation: String
 | 
			
		||||
	}],
 | 
			
		||||
	custom_fields: mongoose.Schema.Types.Mixed
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
NoteSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('note', NoteSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('note', NoteSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,14 @@ import mongoose from 'mongoose';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const NoteFieldSchema = new mongoose.Schema({
 | 
			
		||||
  name: {type: String, index: {unique: true}},
 | 
			
		||||
  qty: Number
 | 
			
		||||
	name: {type: String, index: {unique: true}},
 | 
			
		||||
	qty: Number
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
NoteFieldSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('note_field', NoteFieldSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('note_field', NoteFieldSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,24 +6,24 @@ import UserModel from './user';
 | 
			
		||||
import db from '../db';
 | 
			
		||||
 | 
			
		||||
const SampleSchema = new mongoose.Schema({
 | 
			
		||||
  number: {type: String, index: {unique: true}},
 | 
			
		||||
  type: String,
 | 
			
		||||
  color: String,
 | 
			
		||||
  batch: String,
 | 
			
		||||
  condition: mongoose.Schema.Types.Mixed,
 | 
			
		||||
  material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
 | 
			
		||||
  note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
 | 
			
		||||
  user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel},
 | 
			
		||||
  status: String
 | 
			
		||||
	number: {type: String, index: {unique: true}},
 | 
			
		||||
	type: String,
 | 
			
		||||
	color: String,
 | 
			
		||||
	batch: String,
 | 
			
		||||
	condition: mongoose.Schema.Types.Mixed,
 | 
			
		||||
	material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
 | 
			
		||||
	note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
 | 
			
		||||
	user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel},
 | 
			
		||||
	status: String
 | 
			
		||||
}, {minimize: false});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
SampleSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
SampleSchema.index({material_id: 1});
 | 
			
		||||
SampleSchema.index({note_id: 1});
 | 
			
		||||
SampleSchema.index({user_id: 1});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('sample', SampleSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('sample', SampleSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,21 +3,21 @@ import db from '../db';
 | 
			
		||||
import ModelModel from './model';
 | 
			
		||||
 | 
			
		||||
const UserSchema = new mongoose.Schema({
 | 
			
		||||
  name: {type: String, index: {unique: true}},
 | 
			
		||||
  email: String,
 | 
			
		||||
  pass: String,
 | 
			
		||||
  key: String,
 | 
			
		||||
  level: String,
 | 
			
		||||
  location: String,
 | 
			
		||||
  devices: [String],
 | 
			
		||||
  models: [{type: mongoose.Schema.Types.ObjectId, ref: ModelModel}],
 | 
			
		||||
  status: String
 | 
			
		||||
	name: {type: String, index: {unique: true}},
 | 
			
		||||
	email: String,
 | 
			
		||||
	pass: String,
 | 
			
		||||
	key: String,
 | 
			
		||||
	level: String,
 | 
			
		||||
	location: String,
 | 
			
		||||
	devices: [String],
 | 
			
		||||
	models: [{type: mongoose.Schema.Types.ObjectId, ref: ModelModel}],
 | 
			
		||||
	status: String
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
UserSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
	db.log(req, this);
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('user', UserSchema);
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('user', UserSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,181 +4,181 @@ import HelpModel from '../models/help';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
describe('/help', () => {
 | 
			
		||||
  let server;
 | 
			
		||||
  before(done => TestHelper.before(done));
 | 
			
		||||
  beforeEach(done => server = TestHelper.beforeEach(server, done));
 | 
			
		||||
  afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
  after(done => TestHelper.after(done));
 | 
			
		||||
	let server;
 | 
			
		||||
	before(done => TestHelper.before(done));
 | 
			
		||||
	beforeEach(done => server = TestHelper.beforeEach(server, done));
 | 
			
		||||
	afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
	after(done => TestHelper.after(done));
 | 
			
		||||
 | 
			
		||||
  describe('GET /help/{key}', () => {
 | 
			
		||||
    it('returns the required text', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {text: 'Samples help', level: 'read'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns the required text without authorization if allowed', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fdocumentation',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {text: 'Documentation help', level: 'none'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 404 for an invalid key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/documentation/database',
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 404 for an unknown key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/xxx',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 403 for a text with a higher level than given', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fmodels',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 403
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {api: 'janedoe'},
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unauthorized request if a level is given', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	describe('GET /help/{key}', () => {
 | 
			
		||||
		it('returns the required text', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {basic: 'janedoe'},
 | 
			
		||||
				httpStatus: 200,
 | 
			
		||||
				res: {text: 'Samples help', level: 'read'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('returns the required text without authorization if allowed', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/help/%2Fdocumentation',
 | 
			
		||||
				auth: {basic: 'janedoe'},
 | 
			
		||||
				httpStatus: 200,
 | 
			
		||||
				res: {text: 'Documentation help', level: 'none'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('returns 404 for an invalid key', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/help/documentation/database',
 | 
			
		||||
				httpStatus: 404
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('returns 404 for an unknown key', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/help/xxx',
 | 
			
		||||
				auth: {basic: 'janedoe'},
 | 
			
		||||
				httpStatus: 404
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('returns 403 for a text with a higher level than given', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/help/%2Fmodels',
 | 
			
		||||
				auth: {basic: 'janedoe'},
 | 
			
		||||
				httpStatus: 403
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects an API key', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {api: 'janedoe'},
 | 
			
		||||
				httpStatus: 401,
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects an unauthorized request if a level is given', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  describe('POST /help/{key}', () => {
 | 
			
		||||
    it('changes the required text', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        HelpModel.find({key: '/samples'}).lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(1);
 | 
			
		||||
          should(data[0]).have.only.keys('_id', 'key', 'text', 'level');
 | 
			
		||||
          should(data[0]).property('key', '/samples');
 | 
			
		||||
          should(data[0]).property('text', 'New samples help');
 | 
			
		||||
          should(data[0]).property('level', 'write');
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('saves a new text', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fmaterials',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {text: 'Materials help', level: 'dev'}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(1);
 | 
			
		||||
          should(data[0]).have.only.keys('_id', 'key', 'text', 'level', '__v');
 | 
			
		||||
          should(data[0]).property('key', '/materials');
 | 
			
		||||
          should(data[0]).property('text', 'Materials help');
 | 
			
		||||
          should(data[0]).property('level', 'dev');
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a write user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 403,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unauthorized request', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	describe('POST /help/{key}', () => {
 | 
			
		||||
		it('changes the required text', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'post',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 200,
 | 
			
		||||
				req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
			}).end((err, res) => {
 | 
			
		||||
				if (err) return done(err);
 | 
			
		||||
				should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
				HelpModel.find({key: '/samples'}).lean().exec((err, data) => {
 | 
			
		||||
					if (err) return done(err);
 | 
			
		||||
					should(data).have.lengthOf(1);
 | 
			
		||||
					should(data[0]).have.only.keys('_id', 'key', 'text', 'level');
 | 
			
		||||
					should(data[0]).property('key', '/samples');
 | 
			
		||||
					should(data[0]).property('text', 'New samples help');
 | 
			
		||||
					should(data[0]).property('level', 'write');
 | 
			
		||||
					done();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('saves a new text', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'post',
 | 
			
		||||
				url: '/help/%2Fmaterials',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 200,
 | 
			
		||||
				req: {text: 'Materials help', level: 'dev'}
 | 
			
		||||
			}).end((err, res) => {
 | 
			
		||||
				if (err) return done(err);
 | 
			
		||||
				should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
				HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
 | 
			
		||||
					if (err) return done(err);
 | 
			
		||||
					should(data).have.lengthOf(1);
 | 
			
		||||
					should(data[0]).have.only.keys('_id', 'key', 'text', 'level', '__v');
 | 
			
		||||
					should(data[0]).property('key', '/materials');
 | 
			
		||||
					should(data[0]).property('text', 'Materials help');
 | 
			
		||||
					should(data[0]).property('level', 'dev');
 | 
			
		||||
					done();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects an API key', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'post',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {key: 'admin'},
 | 
			
		||||
				httpStatus: 401,
 | 
			
		||||
				req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects a write user', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'post',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {basic: 'janedoe'},
 | 
			
		||||
				httpStatus: 403,
 | 
			
		||||
				req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects an unauthorized request', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'post',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				httpStatus: 401,
 | 
			
		||||
				req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  describe('DELETE /help/{key}', () => {
 | 
			
		||||
    it('deletes the required entry', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(0);
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a write user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 403
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unauthorized request', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
	describe('DELETE /help/{key}', () => {
 | 
			
		||||
		it('deletes the required entry', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'delete',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 200
 | 
			
		||||
			}).end((err, res) => {
 | 
			
		||||
				if (err) return done(err);
 | 
			
		||||
				should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
				HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
 | 
			
		||||
					if (err) return done(err);
 | 
			
		||||
					should(data).have.lengthOf(0);
 | 
			
		||||
					done();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects an API key', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'delete',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {key: 'admin'},
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects a write user', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'delete',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				auth: {basic: 'janedoe'},
 | 
			
		||||
				httpStatus: 403
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects an unauthorized request', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'delete',
 | 
			
		||||
				url: '/help/%2Fsamples',
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -7,49 +7,49 @@ import globals from '../globals';
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/help/:key', (req, res, next) => {
 | 
			
		||||
  const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
  if (paramError) return res400(paramError, res);
 | 
			
		||||
	const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
	if (paramError) return res400(paramError, res);
 | 
			
		||||
 | 
			
		||||
  HelpModel.findOne(key).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	HelpModel.findOne(key).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    if (data.level !== 'none') {  // check level
 | 
			
		||||
      if (!req.auth(res,
 | 
			
		||||
        Object.values(globals.levels).slice(Object.values(globals.levels).findIndex(e => e === data.level))
 | 
			
		||||
        , 'basic')) return;
 | 
			
		||||
    }
 | 
			
		||||
    res.json(HelpValidate.output(data));
 | 
			
		||||
  })
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		if (data.level !== 'none') {  // check level
 | 
			
		||||
			if (!req.auth(res,
 | 
			
		||||
						  Object.values(globals.levels).slice(Object.values(globals.levels).findIndex(e => e === data.level))
 | 
			
		||||
		, 'basic')) return;
 | 
			
		||||
		}
 | 
			
		||||
		res.json(HelpValidate.output(data));
 | 
			
		||||
	})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/help/:key', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
  const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
  if (paramError) return res400(paramError, res);
 | 
			
		||||
  const {error, value: help} = HelpValidate.input(req.body);
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
	if (paramError) return res400(paramError, res);
 | 
			
		||||
	const {error, value: help} = HelpValidate.input(req.body);
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  HelpModel.findOneAndUpdate(key, help, {upsert: true}).log(req).lean().exec(err => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
	HelpModel.findOneAndUpdate(key, help, {upsert: true}).log(req).lean().exec(err => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		res.json({status: 'OK'});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.delete('/help/:key', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
  const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
  if (paramError) return res400(paramError, res);
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
	if (paramError) return res400(paramError, res);
 | 
			
		||||
 | 
			
		||||
  HelpModel.findOneAndDelete(key).log(req).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
	HelpModel.findOneAndDelete(key).log(req).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		res.json({status: 'OK'});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
module.exports = router;
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -19,169 +19,169 @@ import globals from '../globals';
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/materials', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: filters} =
 | 
			
		||||
    MaterialValidate.query(req.query, ['dev', 'admin'].indexOf(req.authDetails.level) >= 0);
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	const {error, value: filters} =
 | 
			
		||||
		MaterialValidate.query(req.query, ['dev', 'admin'].indexOf(req.authDetails.level) >= 0);
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  MaterialModel.find(filters).sort({name: 1}).populate('group_id').populate('supplier_id')
 | 
			
		||||
    .lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	MaterialModel.find(filters).sort({name: 1}).populate('group_id').populate('supplier_id')
 | 
			
		||||
	.lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
    res.json(_.compact(data.map(e => MaterialValidate.output(e, true))));
 | 
			
		||||
  });
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => MaterialValidate.output(e, true))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get(`/materials/:state(${globals.status.new}|${globals.status.del})`, (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialModel.find({status: req.params.state}).populate('group_id').populate('supplier_id')
 | 
			
		||||
    .lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	MaterialModel.find({status: req.params.state}).populate('group_id').populate('supplier_id')
 | 
			
		||||
	.lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
    res.json(_.compact(data.map(e => MaterialValidate.output(e))));
 | 
			
		||||
  });
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => MaterialValidate.output(e))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialModel.findById(req.params.id).populate('group_id').populate('supplier_id').lean().exec((err, data: any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	MaterialModel.findById(req.params.id).populate('group_id').populate('supplier_id').lean().exec((err, data: any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
    // deleted materials only available for dev/admin
 | 
			
		||||
    if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
    res.json(MaterialValidate.output(data));
 | 
			
		||||
  });
 | 
			
		||||
		// deleted materials only available for dev/admin
 | 
			
		||||
		if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
	res.json(MaterialValidate.output(data));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  let {error, value: material} = MaterialValidate.input(req.body, 'change');
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	let {error, value: material} = MaterialValidate.input(req.body, 'change');
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => {
 | 
			
		||||
    if (!materialData) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    if (materialData.status === 'deleted') {
 | 
			
		||||
      return res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
    }
 | 
			
		||||
    if (material.hasOwnProperty('name') && material.name !== materialData.name) {
 | 
			
		||||
      if (!await nameCheck(material, res, next)) return;
 | 
			
		||||
    }
 | 
			
		||||
    if (material.hasOwnProperty('group')) {
 | 
			
		||||
      material = await groupResolve(material, req, next);
 | 
			
		||||
      if (!material) return;
 | 
			
		||||
    }
 | 
			
		||||
    if (material.hasOwnProperty('supplier')) {
 | 
			
		||||
      material = await supplierResolve(material, req, next);
 | 
			
		||||
      if (!material) return;
 | 
			
		||||
    }
 | 
			
		||||
    if (material.hasOwnProperty('properties')) {
 | 
			
		||||
      if (!await propertiesCheck(material.properties, 'change', res, next,
 | 
			
		||||
        materialData.properties.material_template.toString() !== material.properties.material_template)) return;
 | 
			
		||||
    }
 | 
			
		||||
	MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => {
 | 
			
		||||
		if (!materialData) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		if (materialData.status === 'deleted') {
 | 
			
		||||
			return res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
		}
 | 
			
		||||
		if (material.hasOwnProperty('name') && material.name !== materialData.name) {
 | 
			
		||||
			if (!await nameCheck(material, res, next)) return;
 | 
			
		||||
		}
 | 
			
		||||
		if (material.hasOwnProperty('group')) {
 | 
			
		||||
			material = await groupResolve(material, req, next);
 | 
			
		||||
			if (!material) return;
 | 
			
		||||
		}
 | 
			
		||||
		if (material.hasOwnProperty('supplier')) {
 | 
			
		||||
			material = await supplierResolve(material, req, next);
 | 
			
		||||
			if (!material) return;
 | 
			
		||||
		}
 | 
			
		||||
		if (material.hasOwnProperty('properties')) {
 | 
			
		||||
			if (!await propertiesCheck(material.properties, 'change', res, next,
 | 
			
		||||
									   materialData.properties.material_template.toString() !== material.properties.material_template)) return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
    // check for changes
 | 
			
		||||
    if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), IdValidate.stringify(material))) {
 | 
			
		||||
      material.status = globals.status.new;  // set status to new
 | 
			
		||||
    }
 | 
			
		||||
		// check for changes
 | 
			
		||||
		if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), IdValidate.stringify(material))) {
 | 
			
		||||
			material.status = globals.status.new;  // set status to new
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
    await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true})
 | 
			
		||||
      .log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      res.json(MaterialValidate.output(data));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
		await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true})
 | 
			
		||||
		.log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
			
		||||
			if (err) return next(err);
 | 
			
		||||
			res.json(MaterialValidate.output(data));
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  // check if there are still samples referencing this material
 | 
			
		||||
  SampleModel.find({'material_id': new mongoose.Types.ObjectId(req.params.id), status: {$ne: globals.status.del}})
 | 
			
		||||
    .lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data.length) {
 | 
			
		||||
      return res.status(400).json({status: 'Material still in use'});
 | 
			
		||||
    }
 | 
			
		||||
    MaterialModel.findByIdAndUpdate(req.params.id, {status: globals.status.del})
 | 
			
		||||
      .log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      if (data) {
 | 
			
		||||
        res.json({status: 'OK'});
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        res.status(404).json({status: 'Not found'});
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	// check if there are still samples referencing this material
 | 
			
		||||
	SampleModel.find({'material_id': new mongoose.Types.ObjectId(req.params.id), status: {$ne: globals.status.del}})
 | 
			
		||||
.lean().exec((err, data) => {
 | 
			
		||||
	if (err) return next(err);
 | 
			
		||||
	if (data.length) {
 | 
			
		||||
		return res.status(400).json({status: 'Material still in use'});
 | 
			
		||||
	}
 | 
			
		||||
	MaterialModel.findByIdAndUpdate(req.params.id, {status: globals.status.del})
 | 
			
		||||
	.log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (data) {
 | 
			
		||||
			res.json({status: 'OK'});
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/material/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  setStatus(globals.status.new, req, res, next);
 | 
			
		||||
	setStatus(globals.status.new, req, res, next);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/material/validate/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  setStatus(globals.status.val, req, res, next);
 | 
			
		||||
	setStatus(globals.status.val, req, res, next);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/material/new', async (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  let {error, value: material} = MaterialValidate.input(req.body, 'new');
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	let {error, value: material} = MaterialValidate.input(req.body, 'new');
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  if (!await nameCheck(material, res, next)) return;
 | 
			
		||||
  material = await groupResolve(material, req, next);
 | 
			
		||||
  if (!material) return;
 | 
			
		||||
  material = await supplierResolve(material, req, next);
 | 
			
		||||
  if (!material) return;
 | 
			
		||||
  if (!await propertiesCheck(material.properties, 'new', res, next)) return;
 | 
			
		||||
	if (!await nameCheck(material, res, next)) return;
 | 
			
		||||
	material = await groupResolve(material, req, next);
 | 
			
		||||
	if (!material) return;
 | 
			
		||||
	material = await supplierResolve(material, req, next);
 | 
			
		||||
	if (!material) return;
 | 
			
		||||
	if (!await propertiesCheck(material.properties, 'new', res, next)) return;
 | 
			
		||||
 | 
			
		||||
  material.status = globals.status.new;  // set status to new
 | 
			
		||||
  await new MaterialModel(material).save(async (err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    db.log(req, 'materials', {_id: data._id}, data.toObject());
 | 
			
		||||
    await data.populate('group_id').populate('supplier_id').execPopulate().catch(err => next(err));
 | 
			
		||||
    if (data instanceof Error) return;
 | 
			
		||||
    res.json(MaterialValidate.output(data.toObject()));
 | 
			
		||||
  });
 | 
			
		||||
	material.status = globals.status.new;  // set status to new
 | 
			
		||||
	await new MaterialModel(material).save(async (err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		db.log(req, 'materials', {_id: data._id}, data.toObject());
 | 
			
		||||
		await data.populate('group_id').populate('supplier_id').execPopulate().catch(err => next(err));
 | 
			
		||||
		if (data instanceof Error) return;
 | 
			
		||||
		res.json(MaterialValidate.output(data.toObject()));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/material/groups', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialGroupModel.find().lean().exec((err, data: any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	MaterialGroupModel.find().lean().exec((err, data: any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
    res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name))));
 | 
			
		||||
  });
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/material/suppliers', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialSupplierModel.find().lean().exec((err, data: any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	MaterialSupplierModel.find().lean().exec((err, data: any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
    res.json(_.compact(data.map(e => MaterialValidate.outputSuppliers(e.name))));
 | 
			
		||||
  });
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => MaterialValidate.outputSuppliers(e.name))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -189,81 +189,81 @@ module.exports = router;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async function nameCheck (material, res, next) {  // check if name was already taken
 | 
			
		||||
  const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
  if (materialData instanceof Error) return false;
 | 
			
		||||
  if (materialData) {  // could not find material_id
 | 
			
		||||
    res.status(400).json({status: 'Material name already taken'});
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
	const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
if (materialData instanceof Error) return false;
 | 
			
		||||
if (materialData) {  // could not find material_id
 | 
			
		||||
	res.status(400).json({status: 'Material name already taken'});
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function groupResolve (material, req, next) {
 | 
			
		||||
  const groupData = await MaterialGroupModel.findOneAndUpdate(
 | 
			
		||||
    {name: material.group},
 | 
			
		||||
    {name: material.group},
 | 
			
		||||
    {upsert: true, new: true}
 | 
			
		||||
    ).log(req).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
  if (groupData instanceof Error) return false;
 | 
			
		||||
  material.group_id = groupData._id;
 | 
			
		||||
  delete material.group;
 | 
			
		||||
  return material;
 | 
			
		||||
	const groupData = await MaterialGroupModel.findOneAndUpdate(
 | 
			
		||||
		{name: material.group},
 | 
			
		||||
		{name: material.group},
 | 
			
		||||
		{upsert: true, new: true}
 | 
			
		||||
	).log(req).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
	if (groupData instanceof Error) return false;
 | 
			
		||||
	material.group_id = groupData._id;
 | 
			
		||||
	delete material.group;
 | 
			
		||||
	return material;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function supplierResolve (material, req, next) {
 | 
			
		||||
  const supplierData = await MaterialSupplierModel.findOneAndUpdate(
 | 
			
		||||
    {name: material.supplier},
 | 
			
		||||
    {name: material.supplier},
 | 
			
		||||
    {upsert: true, new: true}
 | 
			
		||||
    ).log(req).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
  if (supplierData instanceof Error) return false;
 | 
			
		||||
  material.supplier_id = supplierData._id;
 | 
			
		||||
  delete material.supplier;
 | 
			
		||||
  return material;
 | 
			
		||||
	const supplierData = await MaterialSupplierModel.findOneAndUpdate(
 | 
			
		||||
		{name: material.supplier},
 | 
			
		||||
		{name: material.supplier},
 | 
			
		||||
		{upsert: true, new: true}
 | 
			
		||||
	).log(req).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
	if (supplierData instanceof Error) return false;
 | 
			
		||||
	material.supplier_id = supplierData._id;
 | 
			
		||||
	delete material.supplier;
 | 
			
		||||
	return material;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validate material properties, returns false if invalid, otherwise template data
 | 
			
		||||
async function propertiesCheck (properties, param, res, next, checkVersion = true) {
 | 
			
		||||
  if (!properties.material_template || !IdValidate.valid(properties.material_template)) {  // template id not found
 | 
			
		||||
    res.status(400).json({status: 'Material template not available'});
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  const materialData = await MaterialTemplateModel.findById(properties.material_template)
 | 
			
		||||
    .lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
  if (materialData instanceof Error) return false;
 | 
			
		||||
  if (!materialData) {  // template not found
 | 
			
		||||
    res.status(400).json({status: 'Material template not available'});
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
	if (!properties.material_template || !IdValidate.valid(properties.material_template)) {  // template id not found
 | 
			
		||||
		res.status(400).json({status: 'Material template not available'});
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	const materialData = await MaterialTemplateModel.findById(properties.material_template)
 | 
			
		||||
	.lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
	if (materialData instanceof Error) return false;
 | 
			
		||||
	if (!materialData) {  // template not found
 | 
			
		||||
		res.status(400).json({status: 'Material template not available'});
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  if (checkVersion) {
 | 
			
		||||
    // get all template versions and check if given is latest
 | 
			
		||||
    const materialVersions = await MaterialTemplateModel.find({first_id: materialData.first_id}).sort({version: -1})
 | 
			
		||||
      .lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
    if (materialVersions instanceof Error) return false;
 | 
			
		||||
    if (properties.material_template !== materialVersions[0]._id.toString()) {  // template not latest
 | 
			
		||||
      res.status(400).json({status: 'Old template version not allowed'});
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	if (checkVersion) {
 | 
			
		||||
		// get all template versions and check if given is latest
 | 
			
		||||
		const materialVersions = await MaterialTemplateModel.find({first_id: materialData.first_id}).sort({version: -1})
 | 
			
		||||
	.lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
	if (materialVersions instanceof Error) return false;
 | 
			
		||||
	if (properties.material_template !== materialVersions[0]._id.toString()) {  // template not latest
 | 
			
		||||
		res.status(400).json({status: 'Old template version not allowed'});
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // validate parameters
 | 
			
		||||
  const {error, value} = ParametersValidate
 | 
			
		||||
    .input(_.omit(properties, 'material_template'), materialData.parameters, param);
 | 
			
		||||
  if (error) {res400(error, res); return false;}
 | 
			
		||||
  Object.keys(value).forEach(key => {
 | 
			
		||||
    properties[key] = value[key];
 | 
			
		||||
  });
 | 
			
		||||
  return materialData;
 | 
			
		||||
	// validate parameters
 | 
			
		||||
	const {error, value} = ParametersValidate
 | 
			
		||||
	.input(_.omit(properties, 'material_template'), materialData.parameters, param);
 | 
			
		||||
	if (error) {res400(error, res); return false;}
 | 
			
		||||
	Object.keys(value).forEach(key => {
 | 
			
		||||
		properties[key] = value[key];
 | 
			
		||||
	});
 | 
			
		||||
	return materialData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setStatus (status, req, res, next) {  // set measurement status
 | 
			
		||||
  MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		res.json({status: 'OK'});
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -16,114 +16,114 @@ import mongoose from "mongoose";
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/measurement/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  MeasurementModel.findById(req.params.id).lean().exec((err, data: any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    // deleted measurements only available for dev/admin
 | 
			
		||||
    if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
	MeasurementModel.findById(req.params.id).lean().exec((err, data: any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		// deleted measurements only available for dev/admin
 | 
			
		||||
		if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
    res.json(MeasurementValidate.output(data, req));
 | 
			
		||||
  });
 | 
			
		||||
	res.json(MeasurementValidate.output(data, req));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: measurement} = MeasurementValidate.input(req.body, 'change');
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	const {error, value: measurement} = MeasurementValidate.input(req.body, 'change');
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
 | 
			
		||||
  if (data instanceof Error) return;
 | 
			
		||||
  if (!data) {
 | 
			
		||||
    return res.status(404).json({status: 'Not found'});
 | 
			
		||||
  }
 | 
			
		||||
  if (data.status === 'deleted') {
 | 
			
		||||
    return res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
  }
 | 
			
		||||
	const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
 | 
			
		||||
	if (data instanceof Error) return;
 | 
			
		||||
	if (!data) {
 | 
			
		||||
		return res.status(404).json({status: 'Not found'});
 | 
			
		||||
	}
 | 
			
		||||
	if (data.status === 'deleted') {
 | 
			
		||||
		return res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // add properties needed for sampleIdCheck
 | 
			
		||||
  measurement.measurement_template = data.measurement_template;
 | 
			
		||||
  measurement.sample_id = data.sample_id;
 | 
			
		||||
  if (!await sampleIdCheck(measurement, req, res, next)) return;
 | 
			
		||||
	// add properties needed for sampleIdCheck
 | 
			
		||||
	measurement.measurement_template = data.measurement_template;
 | 
			
		||||
measurement.sample_id = data.sample_id;
 | 
			
		||||
if (!await sampleIdCheck(measurement, req, res, next)) return;
 | 
			
		||||
 | 
			
		||||
  // check for changes
 | 
			
		||||
  if (measurement.values) {  // fill not changed values from database
 | 
			
		||||
    measurement.values = _.assign({}, data.values, measurement.values);
 | 
			
		||||
    if (!_.isEqual(measurement.values, data.values)) {
 | 
			
		||||
      measurement.status = globals.status.new;  // set status to new
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
// check for changes
 | 
			
		||||
if (measurement.values) {  // fill not changed values from database
 | 
			
		||||
	measurement.values = _.assign({}, data.values, measurement.values);
 | 
			
		||||
	if (!_.isEqual(measurement.values, data.values)) {
 | 
			
		||||
		measurement.status = globals.status.new;  // set status to new
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  if (!await templateCheck(measurement, 'change', res, next)) return;
 | 
			
		||||
  await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true})
 | 
			
		||||
    .log(req).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json(MeasurementValidate.output(data, req));
 | 
			
		||||
  });
 | 
			
		||||
if (!await templateCheck(measurement, 'change', res, next)) return;
 | 
			
		||||
await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true})
 | 
			
		||||
.log(req).lean().exec((err, data) => {
 | 
			
		||||
	if (err) return next(err);
 | 
			
		||||
	res.json(MeasurementValidate.output(data, req));
 | 
			
		||||
});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  MeasurementModel.findById(req.params.id).lean().exec(async (err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    if (!await sampleIdCheck(data, req, res, next)) return;
 | 
			
		||||
    await MeasurementModel.findByIdAndUpdate(req.params.id, {status: globals.status.del})
 | 
			
		||||
      .log(req).lean().exec(err => {
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      return res.json({status: 'OK'});
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	MeasurementModel.findById(req.params.id).lean().exec(async (err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		if (!await sampleIdCheck(data, req, res, next)) return;
 | 
			
		||||
		await MeasurementModel.findByIdAndUpdate(req.params.id, {status: globals.status.del})
 | 
			
		||||
		.log(req).lean().exec(err => {
 | 
			
		||||
			if (err) return next(err);
 | 
			
		||||
			return res.json({status: 'OK'});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/measurement/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data: any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (!data.length) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
	MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data: any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (!data.length) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
    res.json(_.compact(data.map(e => MeasurementValidate.output(e, req, true))));
 | 
			
		||||
  });
 | 
			
		||||
		res.json(_.compact(data.map(e => MeasurementValidate.output(e, req, true))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/measurement/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  setStatus(globals.status.new, req, res, next);
 | 
			
		||||
	setStatus(globals.status.new, req, res, next);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/measurement/validate/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  setStatus(globals.status.val, req, res, next);
 | 
			
		||||
	setStatus(globals.status.val, req, res, next);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/measurement/new', async (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: measurement} = MeasurementValidate.input(req.body, 'new');
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	const {error, value: measurement} = MeasurementValidate.input(req.body, 'new');
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  if (!await sampleIdCheck(measurement, req, res, next)) return;
 | 
			
		||||
  measurement.values = await templateCheck(measurement, 'new', res, next);
 | 
			
		||||
  if (!measurement.values) return;
 | 
			
		||||
	if (!await sampleIdCheck(measurement, req, res, next)) return;
 | 
			
		||||
	measurement.values = await templateCheck(measurement, 'new', res, next);
 | 
			
		||||
	if (!measurement.values) return;
 | 
			
		||||
 | 
			
		||||
  measurement.status = globals.status.new;
 | 
			
		||||
  await new MeasurementModel(measurement).save((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    db.log(req, 'measurements', {_id: data._id}, data.toObject());
 | 
			
		||||
    res.json(MeasurementValidate.output(data.toObject(), req));
 | 
			
		||||
  });
 | 
			
		||||
	measurement.status = globals.status.new;
 | 
			
		||||
	await new MeasurementModel(measurement).save((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		db.log(req, 'measurements', {_id: data._id}, data.toObject());
 | 
			
		||||
		res.json(MeasurementValidate.output(data.toObject(), req));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -132,61 +132,61 @@ module.exports = router;
 | 
			
		||||
 | 
			
		||||
// validate sample_id, returns false if invalid or user has no access for this sample
 | 
			
		||||
async function sampleIdCheck (measurement, req, res, next) {
 | 
			
		||||
  const sampleData = await SampleModel.findById(measurement.sample_id)
 | 
			
		||||
    .lean().exec().catch(err => {next(err); return false;}) as any;
 | 
			
		||||
  if (!sampleData) {  // sample_id not found
 | 
			
		||||
    res.status(400).json({status: 'Sample id not available'});
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
  // sample does not belong to user
 | 
			
		||||
  return !(sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic'));
 | 
			
		||||
	const sampleData = await SampleModel.findById(measurement.sample_id)
 | 
			
		||||
	.lean().exec().catch(err => {next(err); return false;}) as any;
 | 
			
		||||
	if (!sampleData) {  // sample_id not found
 | 
			
		||||
		res.status(400).json({status: 'Sample id not available'});
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	// sample does not belong to user
 | 
			
		||||
	return !(sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic'));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validate measurement_template and values, returns values, true if values are {} or false if invalid,
 | 
			
		||||
// param for 'new'/'change'
 | 
			
		||||
async function templateCheck (measurement, param, res, next) {
 | 
			
		||||
  const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template)
 | 
			
		||||
    .lean().exec().catch(err => {next(err); return false;}) as any;
 | 
			
		||||
  if (!templateData) {  // template not found
 | 
			
		||||
    res.status(400).json({status: 'Measurement template not available'});
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
	const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template)
 | 
			
		||||
	.lean().exec().catch(err => {next(err); return false;}) as any;
 | 
			
		||||
	if (!templateData) {  // template not found
 | 
			
		||||
		res.status(400).json({status: 'Measurement template not available'});
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // fill not given values for new measurements
 | 
			
		||||
  if (param === 'new') {
 | 
			
		||||
    // get all template versions and check if given is latest
 | 
			
		||||
    const templateVersions = await MeasurementTemplateModel.find({first_id: templateData.first_id}).sort({version: -1})
 | 
			
		||||
      .lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
    if (templateVersions instanceof Error) return false;
 | 
			
		||||
    if (measurement.measurement_template !== templateVersions[0]._id.toString()) {  // template not latest
 | 
			
		||||
      res.status(400).json({status: 'Old template version not allowed'});
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
	// fill not given values for new measurements
 | 
			
		||||
	if (param === 'new') {
 | 
			
		||||
		// get all template versions and check if given is latest
 | 
			
		||||
		const templateVersions = await MeasurementTemplateModel.find({first_id: templateData.first_id}).sort({version: -1})
 | 
			
		||||
	.lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
	if (templateVersions instanceof Error) return false;
 | 
			
		||||
	if (measurement.measurement_template !== templateVersions[0]._id.toString()) {  // template not latest
 | 
			
		||||
		res.status(400).json({status: 'Old template version not allowed'});
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    if (Object.keys(measurement.values).length === 0) {
 | 
			
		||||
      res.status(400).json({status: 'At least one value is required'});
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
    const fillValues = {};  // initialize not given values with null
 | 
			
		||||
    templateData.parameters.forEach(parameter => {
 | 
			
		||||
      fillValues[parameter.name] = null;
 | 
			
		||||
    });
 | 
			
		||||
    measurement.values = _.assign({}, fillValues, measurement.values);
 | 
			
		||||
  }
 | 
			
		||||
	if (Object.keys(measurement.values).length === 0) {
 | 
			
		||||
		res.status(400).json({status: 'At least one value is required'});
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	const fillValues = {};  // initialize not given values with null
 | 
			
		||||
	templateData.parameters.forEach(parameter => {
 | 
			
		||||
		fillValues[parameter.name] = null;
 | 
			
		||||
	});
 | 
			
		||||
	measurement.values = _.assign({}, fillValues, measurement.values);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // validate values
 | 
			
		||||
  const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null');
 | 
			
		||||
  if (error) {res400(error, res); return false;}
 | 
			
		||||
  return value || true;
 | 
			
		||||
	// validate values
 | 
			
		||||
	const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null');
 | 
			
		||||
	if (error) {res400(error, res); return false;}
 | 
			
		||||
	return value || true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setStatus (status, req, res, next) {  // set measurement status
 | 
			
		||||
  MeasurementModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	MeasurementModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		res.json({status: 'OK'});
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -13,162 +13,162 @@ import mongoose from "mongoose";
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/model/groups', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  let conditions: any = [{}, {}];
 | 
			
		||||
  if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) {  // if not dev or admin, user has to possess model rights
 | 
			
		||||
    conditions = [
 | 
			
		||||
      {'models._id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}},
 | 
			
		||||
      {group: true, 'models.$': true}
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
  ModelModel.find(...conditions).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	let conditions: any = [{}, {}];
 | 
			
		||||
	if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) {  // if not dev or admin, user has to possess model rights
 | 
			
		||||
		conditions = [
 | 
			
		||||
			{'models._id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}},
 | 
			
		||||
			{group: true, 'models.$': true}
 | 
			
		||||
		]
 | 
			
		||||
	}
 | 
			
		||||
	ModelModel.find(...conditions).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
    res.json(_.compact(data.map(e => ModelValidate.output(e))));
 | 
			
		||||
  });
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => ModelValidate.output(e))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/model/:group', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: model} = ModelValidate.input(req.body);
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	const {error, value: model} = ModelValidate.input(req.body);
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (data) {  // group exists
 | 
			
		||||
      if (data.models.find(e => e.name === model.name)) {  // name exists, overwrite
 | 
			
		||||
        ModelModel.findOneAndUpdate(
 | 
			
		||||
          {$and: [{group: req.params.group}, {'models.name': model.name}]},
 | 
			
		||||
          {'models.$': model},
 | 
			
		||||
          {upsert: true}).log(req).lean().exec(err => {
 | 
			
		||||
          if (err) return next(err);
 | 
			
		||||
          res.json({status: 'OK'})
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      else {  // create new
 | 
			
		||||
        ModelModel.findOneAndUpdate(
 | 
			
		||||
          {group: req.params.group},
 | 
			
		||||
          {$push: {models: model as never}}
 | 
			
		||||
        ).log(req).lean().exec(err => {
 | 
			
		||||
          if (err) return next(err);
 | 
			
		||||
          res.json({status: 'OK'});
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {  // create new group
 | 
			
		||||
      new ModelModel({group: req.params.group, models: [model]}).save((err, data) => {
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        db.log(req, 'models', {_id: data._id}, data.toObject());
 | 
			
		||||
        res.json({status: 'OK'});
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
		if (data) {  // group exists
 | 
			
		||||
			if (data.models.find(e => e.name === model.name)) {  // name exists, overwrite
 | 
			
		||||
				ModelModel.findOneAndUpdate(
 | 
			
		||||
					{$and: [{group: req.params.group}, {'models.name': model.name}]},
 | 
			
		||||
					{'models.$': model},
 | 
			
		||||
					{upsert: true}).log(req).lean().exec(err => {
 | 
			
		||||
						if (err) return next(err);
 | 
			
		||||
						res.json({status: 'OK'})
 | 
			
		||||
					});
 | 
			
		||||
			}
 | 
			
		||||
			else {  // create new
 | 
			
		||||
				ModelModel.findOneAndUpdate(
 | 
			
		||||
					{group: req.params.group},
 | 
			
		||||
					{$push: {models: model as never}}
 | 
			
		||||
				).log(req).lean().exec(err => {
 | 
			
		||||
					if (err) return next(err);
 | 
			
		||||
					res.json({status: 'OK'});
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {  // create new group
 | 
			
		||||
			new ModelModel({group: req.params.group, models: [model]}).save((err, data) => {
 | 
			
		||||
				if (err) return next(err);
 | 
			
		||||
				db.log(req, 'models', {_id: data._id}, data.toObject());
 | 
			
		||||
				res.json({status: 'OK'});
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.delete('/model/:group(((?!file)[^\\/]+?))/:name', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (!data || !data.models.find(e => e.name === req.params.name)) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    // delete all references in user.models
 | 
			
		||||
    UserModel.updateMany({}, {$pull: {models: data.models.find(e => e.name === req.params.name)._id as never}},
 | 
			
		||||
      { multi: true }).log(req).lean().exec(err => {
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      if (data.models.length > 1) {  // only remove model
 | 
			
		||||
        ModelModel.findOneAndUpdate(
 | 
			
		||||
          {group: req.params.group},
 | 
			
		||||
          {$pull: {models: data.models.find(e => e.name === req.params.name) as never}}
 | 
			
		||||
        ).log(req).lean().exec(err => {
 | 
			
		||||
          if (err) return next(err);
 | 
			
		||||
          res.json({status: 'OK'})
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      else {  // remove document
 | 
			
		||||
        ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => {
 | 
			
		||||
          if (err) return next(err);
 | 
			
		||||
          res.json({status: 'OK'})
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
		if (!data || !data.models.find(e => e.name === req.params.name)) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		// delete all references in user.models
 | 
			
		||||
		UserModel.updateMany({}, {$pull: {models: data.models.find(e => e.name === req.params.name)._id as never}},
 | 
			
		||||
							 { multi: true }).log(req).lean().exec(err => {
 | 
			
		||||
								 if (err) return next(err);
 | 
			
		||||
								 if (data.models.length > 1) {  // only remove model
 | 
			
		||||
									 ModelModel.findOneAndUpdate(
 | 
			
		||||
										 {group: req.params.group},
 | 
			
		||||
										 {$pull: {models: data.models.find(e => e.name === req.params.name) as never}}
 | 
			
		||||
									 ).log(req).lean().exec(err => {
 | 
			
		||||
										 if (err) return next(err);
 | 
			
		||||
										 res.json({status: 'OK'})
 | 
			
		||||
									 });
 | 
			
		||||
								 }
 | 
			
		||||
								 else {  // remove document
 | 
			
		||||
									 ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => {
 | 
			
		||||
										 if (err) return next(err);
 | 
			
		||||
										 res.json({status: 'OK'})
 | 
			
		||||
									 });
 | 
			
		||||
								 }
 | 
			
		||||
							 });
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/model/files', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  ModelFileModel.find().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json(data.map(e => ModelValidate.fileOutput(e)));
 | 
			
		||||
  });
 | 
			
		||||
	ModelFileModel.find().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		res.json(data.map(e => ModelValidate.fileOutput(e)));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/model/file/:name', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  ModelFileModel.findOne({name: req.params.name}).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.set('Content-Type', 'application/octet-stream');
 | 
			
		||||
      res.send(data.data.buffer);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
	ModelFileModel.findOne({name: req.params.name}).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (data) {
 | 
			
		||||
			res.set('Content-Type', 'application/octet-stream');
 | 
			
		||||
			res.send(data.data.buffer);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/model/file/:name', bodyParser.raw({limit: '50mb'}), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  ModelFileModel.replaceOne({name: req.params.name}, {name: req.params.name, data: req.body}).setOptions({upsert: true})
 | 
			
		||||
    .lean().exec(err => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
	ModelFileModel.replaceOne({name: req.params.name}, {name: req.params.name, data: req.body}).setOptions({upsert: true})
 | 
			
		||||
	.lean().exec(err => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		res.json({status: 'OK'});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.delete('/model/file/:name', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  ModelFileModel.findOneAndDelete({name: req.params.name}).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json({status: 'OK'});
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
	ModelFileModel.findOneAndDelete({name: req.params.name}).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (data) {
 | 
			
		||||
			res.json({status: 'OK'});
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/model/authorized/:url', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) {  // if not dev or admin, user has to possess model rights
 | 
			
		||||
    ModelModel.findOne({models: { $elemMatch: {
 | 
			
		||||
      url: decodeURIComponent(req.params.url),
 | 
			
		||||
      '_id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}
 | 
			
		||||
    }}}).lean().exec((err, data) => {
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      if (data) {
 | 
			
		||||
        res.json({status: 'OK'});
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  }
 | 
			
		||||
	if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) {  // if not dev or admin, user has to possess model rights
 | 
			
		||||
		ModelModel.findOne({models: { $elemMatch: {
 | 
			
		||||
			url: decodeURIComponent(req.params.url),
 | 
			
		||||
			'_id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}
 | 
			
		||||
		}}}).lean().exec((err, data) => {
 | 
			
		||||
			if (err) return next(err);
 | 
			
		||||
			if (data) {
 | 
			
		||||
				res.json({status: 'OK'});
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				res.status(403).json({status: 'Forbidden'});
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		res.json({status: 'OK'});
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
module.exports = router;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,254 +4,254 @@ import db from '../db';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
describe('/', () => {
 | 
			
		||||
  let server;
 | 
			
		||||
  before(done => TestHelper.before(done));
 | 
			
		||||
  beforeEach(done => server = TestHelper.beforeEach(server, done));
 | 
			
		||||
  afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
  after(done => TestHelper.after(done));
 | 
			
		||||
	let server;
 | 
			
		||||
	before(done => TestHelper.before(done));
 | 
			
		||||
	beforeEach(done => server = TestHelper.beforeEach(server, done));
 | 
			
		||||
	afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
	after(done => TestHelper.after(done));
 | 
			
		||||
 | 
			
		||||
  describe('GET /', () => {
 | 
			
		||||
    it('returns the root message', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/',
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {status: 'API server up and running!'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	describe('GET /', () => {
 | 
			
		||||
		it('returns the root message', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/',
 | 
			
		||||
				httpStatus: 200,
 | 
			
		||||
				res: {status: 'API server up and running!'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  describe('GET /changelog/{timestamp}/{page}/{pagesize}', () => {
 | 
			
		||||
    it('returns the first page', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/0/2',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).have.lengthOf(2);
 | 
			
		||||
        should(res.body[0].date).be.eql('1979-07-28T06:04:51.000Z');
 | 
			
		||||
        should(res.body[1].date).be.eql('1979-07-28T06:04:50.000Z');
 | 
			
		||||
        should(res.body).matchEach(log => {
 | 
			
		||||
          should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
 | 
			
		||||
          should(log).have.property('_id').be.type('string');
 | 
			
		||||
          should(log).have.property('action', 'PUT /sample/400000000000000000000001');
 | 
			
		||||
          should(log).have.property('collection', 'samples');
 | 
			
		||||
          should(log).have.property('conditions', {_id: '400000000000000000000001'});
 | 
			
		||||
          should(log).have.property('data', {type: 'processed', status: 0});
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns another page', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/1/2',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).have.lengthOf(1);
 | 
			
		||||
        should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z');
 | 
			
		||||
        should(res.body).matchEach(log => {
 | 
			
		||||
          should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
 | 
			
		||||
          should(log).have.property('_id').be.type('string');
 | 
			
		||||
          should(log).have.property('action', 'PUT /sample/400000000000000000000001');
 | 
			
		||||
          should(log).have.property('collection', 'samples');
 | 
			
		||||
          should(log).have.property('conditions', {_id: '400000000000000000000001'});
 | 
			
		||||
          should(log).have.property('data', {type: 'processed', status: 0});
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns an empty array for a page with no results', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).have.lengthOf(0);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects invalid ids', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/12000003000000h000000000/10/2',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        res: {status: 'Invalid body format', details: 'Invalid object id'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects negative page numbers', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/-10/2',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        res: {status: 'Invalid body format', details: '"page" must be greater than or equal to 0'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects negative pagesizes', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/10/-2',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        res: {status: 'Invalid body format', details: '"pagesize" must be greater than or equal to 0'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects request from a write user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 403
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	describe('GET /changelog/{timestamp}/{page}/{pagesize}', () => {
 | 
			
		||||
		it('returns the first page', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/0/2',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 200
 | 
			
		||||
			}).end((err, res) => {
 | 
			
		||||
				if (err) return done(err);
 | 
			
		||||
				should(res.body).have.lengthOf(2);
 | 
			
		||||
				should(res.body[0].date).be.eql('1979-07-28T06:04:51.000Z');
 | 
			
		||||
				should(res.body[1].date).be.eql('1979-07-28T06:04:50.000Z');
 | 
			
		||||
				should(res.body).matchEach(log => {
 | 
			
		||||
					should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
 | 
			
		||||
					should(log).have.property('_id').be.type('string');
 | 
			
		||||
					should(log).have.property('action', 'PUT /sample/400000000000000000000001');
 | 
			
		||||
					should(log).have.property('collection', 'samples');
 | 
			
		||||
					should(log).have.property('conditions', {_id: '400000000000000000000001'});
 | 
			
		||||
					should(log).have.property('data', {type: 'processed', status: 0});
 | 
			
		||||
				});
 | 
			
		||||
				done();
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('returns another page', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/1/2',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 200
 | 
			
		||||
			}).end((err, res) => {
 | 
			
		||||
				if (err) return done(err);
 | 
			
		||||
				should(res.body).have.lengthOf(1);
 | 
			
		||||
				should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z');
 | 
			
		||||
				should(res.body).matchEach(log => {
 | 
			
		||||
					should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
 | 
			
		||||
					should(log).have.property('_id').be.type('string');
 | 
			
		||||
					should(log).have.property('action', 'PUT /sample/400000000000000000000001');
 | 
			
		||||
					should(log).have.property('collection', 'samples');
 | 
			
		||||
					should(log).have.property('conditions', {_id: '400000000000000000000001'});
 | 
			
		||||
					should(log).have.property('data', {type: 'processed', status: 0});
 | 
			
		||||
					done();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('returns an empty array for a page with no results', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 200
 | 
			
		||||
			}).end((err, res) => {
 | 
			
		||||
				if (err) return done(err);
 | 
			
		||||
				should(res.body).have.lengthOf(0);
 | 
			
		||||
				done();
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects invalid ids', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/12000003000000h000000000/10/2',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 400,
 | 
			
		||||
				res: {status: 'Invalid body format', details: 'Invalid object id'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects negative page numbers', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/-10/2',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 400,
 | 
			
		||||
				res: {status: 'Invalid body format', details: '"page" must be greater than or equal to 0'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects negative pagesizes', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/10/-2',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 400,
 | 
			
		||||
				res: {status: 'Invalid body format', details: '"pagesize" must be greater than or equal to 0'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects request from a write user', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
				auth: {basic: 'janedoe'},
 | 
			
		||||
				httpStatus: 403
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects requests from an API key', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
				auth: {key: 'admin'},
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('rejects unauthorized requests', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/changelog/120000030000000000000000/10/2',
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  describe('Unknown routes', () => {
 | 
			
		||||
    it('return a 404 message', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/unknownroute',
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	describe('Unknown routes', () => {
 | 
			
		||||
		it('return a 404 message', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/unknownroute',
 | 
			
		||||
				httpStatus: 404
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  describe('An unauthorized request', () => {
 | 
			
		||||
    it('returns a 401 message', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/authorized',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('does not work with correct username', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/authorized',
 | 
			
		||||
        auth: {basic: {name: 'admin', pass: 'Abc123!!'}},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('does not work with incorrect username', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/authorized',
 | 
			
		||||
        auth: {basic: {name: 'adminxx', pass: 'Abc123!!'}},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('does not work with a deleted user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/authorized',
 | 
			
		||||
        auth: {basic: {name: 'customerold', pass: 'Xyz890*)'}},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	describe('An unauthorized request', () => {
 | 
			
		||||
		it('returns a 401 message', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/authorized',
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('does not work with correct username', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/authorized',
 | 
			
		||||
				auth: {basic: {name: 'admin', pass: 'Abc123!!'}},
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('does not work with incorrect username', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/authorized',
 | 
			
		||||
				auth: {basic: {name: 'adminxx', pass: 'Abc123!!'}},
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('does not work with a deleted user', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/authorized',
 | 
			
		||||
				auth: {basic: {name: 'customerold', pass: 'Xyz890*)'}},
 | 
			
		||||
				httpStatus: 401
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  describe('An authorized request', () => {
 | 
			
		||||
    it('works with an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/authorized',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {status: 'Authorization successful', method: 'key', level: 'admin', user_id: '000000000000000000000003'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('works with basic auth', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/authorized',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	describe('An authorized request', () => {
 | 
			
		||||
		it('works with an API key', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/authorized',
 | 
			
		||||
				auth: {key: 'admin'},
 | 
			
		||||
				httpStatus: 200,
 | 
			
		||||
				res: {status: 'Authorization successful', method: 'key', level: 'admin', user_id: '000000000000000000000003'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		it('works with basic auth', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'get',
 | 
			
		||||
				url: '/authorized',
 | 
			
		||||
				auth: {basic: 'admin'},
 | 
			
		||||
				httpStatus: 200,
 | 
			
		||||
				res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  describe('An invalid JSON body', () => {
 | 
			
		||||
    it('is rejected', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/',
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        reqType: 'json',
 | 
			
		||||
        req: '{"xxx"}',
 | 
			
		||||
        res: {status: 'Invalid JSON body'}
 | 
			
		||||
      });
 | 
			
		||||
	describe('An invalid JSON body', () => {
 | 
			
		||||
		it('is rejected', done => {
 | 
			
		||||
			TestHelper.request(server, done, {
 | 
			
		||||
				method: 'post',
 | 
			
		||||
				url: '/',
 | 
			
		||||
				httpStatus: 400,
 | 
			
		||||
				reqType: 'json',
 | 
			
		||||
				req: '{"xxx"}',
 | 
			
		||||
				res: {status: 'Invalid JSON body'}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  // describe('A not connected database', () => {  // RUN AS LAST OR RECONNECT DATABASE!!
 | 
			
		||||
  //   it('resolves to an 500 error', done => {
 | 
			
		||||
  //     db.disconnect(() => {
 | 
			
		||||
  //       TestHelper.request(server, done, {
 | 
			
		||||
  //         method: 'get',
 | 
			
		||||
  //         url: '/',
 | 
			
		||||
  //         httpStatus: 500
 | 
			
		||||
  //       });
 | 
			
		||||
  //     });
 | 
			
		||||
  //   });
 | 
			
		||||
  // });
 | 
			
		||||
	// describe('A not connected database', () => {  // RUN AS LAST OR RECONNECT DATABASE!!
 | 
			
		||||
	//   it('resolves to an 500 error', done => {
 | 
			
		||||
	//     db.disconnect(() => {
 | 
			
		||||
	//       TestHelper.request(server, done, {
 | 
			
		||||
	//         method: 'get',
 | 
			
		||||
	//         url: '/',
 | 
			
		||||
	//         httpStatus: 500
 | 
			
		||||
	//       });
 | 
			
		||||
	//     });
 | 
			
		||||
	//   });
 | 
			
		||||
	// });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('The /api/{url} redirect', () => {
 | 
			
		||||
  let server;
 | 
			
		||||
  let counter = 0;  // count number of current test method
 | 
			
		||||
  before(done => {
 | 
			
		||||
    process.env.port = '2999';
 | 
			
		||||
    db.connect('test', done);
 | 
			
		||||
  });
 | 
			
		||||
  beforeEach(done => {
 | 
			
		||||
    process.env.NODE_ENV = counter === 1 ? 'production' : 'test';
 | 
			
		||||
    counter ++;
 | 
			
		||||
    server = TestHelper.beforeEach(server, done);
 | 
			
		||||
  });
 | 
			
		||||
  afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
  after(done => TestHelper.after(done));
 | 
			
		||||
	let server;
 | 
			
		||||
	let counter = 0;  // count number of current test method
 | 
			
		||||
	before(done => {
 | 
			
		||||
		process.env.port = '2999';
 | 
			
		||||
		db.connect('test', done);
 | 
			
		||||
	});
 | 
			
		||||
	beforeEach(done => {
 | 
			
		||||
		process.env.NODE_ENV = counter === 1 ? 'production' : 'test';
 | 
			
		||||
		counter ++;
 | 
			
		||||
		server = TestHelper.beforeEach(server, done);
 | 
			
		||||
	});
 | 
			
		||||
	afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
	after(done => TestHelper.after(done));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  it('returns the right method', done => {
 | 
			
		||||
    TestHelper.request(server, done, {
 | 
			
		||||
      method: 'get',
 | 
			
		||||
      url: '/api/authorized',
 | 
			
		||||
      auth: {basic: 'admin'},
 | 
			
		||||
      httpStatus: 200,
 | 
			
		||||
      res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  // it('is disabled in production', done => {
 | 
			
		||||
  //   TestHelper.request(server, done, {
 | 
			
		||||
  //     method: 'get',
 | 
			
		||||
  //     url: '/api/authorized',
 | 
			
		||||
  //     auth: {basic: 'admin'},
 | 
			
		||||
  //     httpStatus: 404
 | 
			
		||||
  //   });
 | 
			
		||||
  // });
 | 
			
		||||
});
 | 
			
		||||
	it('returns the right method', done => {
 | 
			
		||||
		TestHelper.request(server, done, {
 | 
			
		||||
			method: 'get',
 | 
			
		||||
			url: '/api/authorized',
 | 
			
		||||
			auth: {basic: 'admin'},
 | 
			
		||||
			httpStatus: 200,
 | 
			
		||||
			res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
	// it('is disabled in production', done => {
 | 
			
		||||
	//   TestHelper.request(server, done, {
 | 
			
		||||
	//     method: 'get',
 | 
			
		||||
	//     url: '/api/authorized',
 | 
			
		||||
	//     auth: {basic: 'admin'},
 | 
			
		||||
	//     httpStatus: 404
 | 
			
		||||
	//   });
 | 
			
		||||
	// });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -9,37 +9,37 @@ import _ from 'lodash';
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/', (req, res) => {
 | 
			
		||||
  res.json({status: 'API server up and running!'});
 | 
			
		||||
	res.json({status: 'API server up and running!'});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/authorized', (req, res) => {
 | 
			
		||||
  if (!req.auth(res, Object.values(globals.levels))) return;
 | 
			
		||||
  res.json({
 | 
			
		||||
    status: 'Authorization successful',
 | 
			
		||||
    method: req.authDetails.method,
 | 
			
		||||
    level: req.authDetails.level,
 | 
			
		||||
    user_id: req.authDetails.id
 | 
			
		||||
  });
 | 
			
		||||
	if (!req.auth(res, Object.values(globals.levels))) return;
 | 
			
		||||
	res.json({
 | 
			
		||||
		status: 'Authorization successful',
 | 
			
		||||
		method: req.authDetails.method,
 | 
			
		||||
		level: req.authDetails.level,
 | 
			
		||||
		user_id: req.authDetails.id
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/changelog/:id/:page?/:pagesize?', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: options} = RootValidate.changelogParams({
 | 
			
		||||
    id: req.params.id,
 | 
			
		||||
    page: req.params.page,
 | 
			
		||||
    pagesize: req.params.pagesize
 | 
			
		||||
  });
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	const {error, value: options} = RootValidate.changelogParams({
 | 
			
		||||
		id: req.params.id,
 | 
			
		||||
		page: req.params.page,
 | 
			
		||||
		pagesize: req.params.pagesize
 | 
			
		||||
	});
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  ChangelogModel.find({_id: {$lte: mongoose.Types.ObjectId(options.id)}})
 | 
			
		||||
    .sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize)
 | 
			
		||||
    .lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	ChangelogModel.find({_id: {$lte: mongoose.Types.ObjectId(options.id)}})
 | 
			
		||||
	.sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize)
 | 
			
		||||
	.lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
    res.json(_.compact(data.map(e => RootValidate.changelogOutput(e))));
 | 
			
		||||
  });
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => RootValidate.changelogOutput(e))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1558
									
								
								src/routes/sample.ts
									
									
									
									
									
								
							
							
						
						
									
										1558
									
								
								src/routes/sample.ts
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -18,141 +18,141 @@ import db from '../db';
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/template/:collection(measurements|conditions|materials)', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  req.params.collection = req.params.collection.replace(/s$/g, '');  // remove trailing s
 | 
			
		||||
  model(req).find({}).lean().exec((err, data) => {
 | 
			
		||||
     if (err) next (err);
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
     res.json(_.compact(data.map(e => TemplateValidate.output(e))));
 | 
			
		||||
  });
 | 
			
		||||
	req.params.collection = req.params.collection.replace(/s$/g, '');  // remove trailing s
 | 
			
		||||
	model(req).find({}).lean().exec((err, data) => {
 | 
			
		||||
		if (err) next (err);
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => TemplateValidate.output(e))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/template/:collection(measurement|condition|material)/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  model(req).findById(req.params.id).lean().exec((err, data) => {
 | 
			
		||||
    if (err) next (err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json(TemplateValidate.output(data));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
	model(req).findById(req.params.id).lean().exec((err, data) => {
 | 
			
		||||
		if (err) next (err);
 | 
			
		||||
		if (data) {
 | 
			
		||||
			res.json(TemplateValidate.output(data));
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/template/:collection(measurement|condition|material)/' + IdValidate.parameter(),
 | 
			
		||||
  async (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
		   async (req, res, next) => {
 | 
			
		||||
			   if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: template} = TemplateValidate.input(req.body, 'change');
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
			   const {error, value: template} = TemplateValidate.input(req.body, 'change');
 | 
			
		||||
			   if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  // find given template
 | 
			
		||||
  const templateRef = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
 | 
			
		||||
  if (templateRef instanceof Error) return;
 | 
			
		||||
  if (!templateRef) {
 | 
			
		||||
    return res.status(404).json({status: 'Not found'});
 | 
			
		||||
  }
 | 
			
		||||
  // find latest version
 | 
			
		||||
  const templateData = await model(req).findOne({first_id: templateRef.first_id}).sort({version: -1})
 | 
			
		||||
    .lean().exec().catch(err => {next(err);}) as any;
 | 
			
		||||
  if (templateData instanceof Error) return;
 | 
			
		||||
  if (!templateData) {
 | 
			
		||||
    return res.status(404).json({status: 'Not found'});
 | 
			
		||||
  }
 | 
			
		||||
			   // find given template
 | 
			
		||||
			   const templateRef = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
 | 
			
		||||
			   if (templateRef instanceof Error) return;
 | 
			
		||||
			   if (!templateRef) {
 | 
			
		||||
				   return res.status(404).json({status: 'Not found'});
 | 
			
		||||
			   }
 | 
			
		||||
			   // find latest version
 | 
			
		||||
			   const templateData = await model(req).findOne({first_id: templateRef.first_id}).sort({version: -1})
 | 
			
		||||
			   .lean().exec().catch(err => {next(err);}) as any;
 | 
			
		||||
			   if (templateData instanceof Error) return;
 | 
			
		||||
			   if (!templateData) {
 | 
			
		||||
				   return res.status(404).json({status: 'Not found'});
 | 
			
		||||
			   }
 | 
			
		||||
 | 
			
		||||
  if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) {  // data was changed
 | 
			
		||||
    if (!template.parameters || _.isEqual(templateData.parameters, template.parameters)) {  // only name was changed
 | 
			
		||||
      model(req).findByIdAndUpdate(req.params.id, {name: template.name}, {new: true})
 | 
			
		||||
        .log(req).lean().exec((err, data) => {
 | 
			
		||||
        if (err) next (err);
 | 
			
		||||
        res.json(TemplateValidate.output(data));
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else if (template.parameters.filter((e, i) => _.isEqual(e.range, templateData.parameters[i].range)).length
 | 
			
		||||
      === templateData.parameters.length) {  // only names changed
 | 
			
		||||
      const changedParameterNames = template.parameters.map((e, i) => (  // list of new names
 | 
			
		||||
          {name: e.name, index: i, oldName: templateData.parameters[i].name}
 | 
			
		||||
        )).filter(e => e.name !== e.oldName);
 | 
			
		||||
			   if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) {  // data was changed
 | 
			
		||||
				   if (!template.parameters || _.isEqual(templateData.parameters, template.parameters)) {  // only name was changed
 | 
			
		||||
					   model(req).findByIdAndUpdate(req.params.id, {name: template.name}, {new: true})
 | 
			
		||||
					   .log(req).lean().exec((err, data) => {
 | 
			
		||||
						   if (err) next (err);
 | 
			
		||||
						   res.json(TemplateValidate.output(data));
 | 
			
		||||
					   });
 | 
			
		||||
				   }
 | 
			
		||||
				   else if (template.parameters.filter((e, i) => _.isEqual(e.range, templateData.parameters[i].range)).length
 | 
			
		||||
							=== templateData.parameters.length) {  // only names changed
 | 
			
		||||
								const changedParameterNames = template.parameters.map((e, i) => (  // list of new names
 | 
			
		||||
																								 {name: e.name, index: i, oldName: templateData.parameters[i].name}
 | 
			
		||||
																								)).filter(e => e.name !== e.oldName);
 | 
			
		||||
 | 
			
		||||
      // custom mappings for different collections
 | 
			
		||||
      let targetModel;  // model of the collection where the template is used
 | 
			
		||||
      let pathPrefix;   // path to the parameters in use
 | 
			
		||||
      let templatePath; // complete path of the template property
 | 
			
		||||
      switch (req.params.collection) {
 | 
			
		||||
        case 'condition':
 | 
			
		||||
          targetModel = SampleModel;
 | 
			
		||||
          pathPrefix = 'condition.';
 | 
			
		||||
          templatePath = 'condition.condition_template';
 | 
			
		||||
          break;
 | 
			
		||||
        case 'measurement':
 | 
			
		||||
          targetModel = MeasurementModel;
 | 
			
		||||
          pathPrefix = 'values.';
 | 
			
		||||
          templatePath = 'measurement_template';
 | 
			
		||||
          break;
 | 
			
		||||
        case 'material':
 | 
			
		||||
          targetModel = MaterialModel;
 | 
			
		||||
          pathPrefix = 'properties.';
 | 
			
		||||
          templatePath = 'properties.material_template';
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
																								// custom mappings for different collections
 | 
			
		||||
																								let targetModel;  // model of the collection where the template is used
 | 
			
		||||
																							let pathPrefix;   // path to the parameters in use
 | 
			
		||||
																							let templatePath; // complete path of the template property
 | 
			
		||||
																							switch (req.params.collection) {
 | 
			
		||||
																								case 'condition':
 | 
			
		||||
																									targetModel = SampleModel;
 | 
			
		||||
																								pathPrefix = 'condition.';
 | 
			
		||||
																								templatePath = 'condition.condition_template';
 | 
			
		||||
																								break;
 | 
			
		||||
																								case 'measurement':
 | 
			
		||||
																									targetModel = MeasurementModel;
 | 
			
		||||
																								pathPrefix = 'values.';
 | 
			
		||||
																								templatePath = 'measurement_template';
 | 
			
		||||
																								break;
 | 
			
		||||
																								case 'material':
 | 
			
		||||
																									targetModel = MaterialModel;
 | 
			
		||||
																								pathPrefix = 'properties.';
 | 
			
		||||
																								templatePath = 'properties.material_template';
 | 
			
		||||
																								break;
 | 
			
		||||
																							}
 | 
			
		||||
 | 
			
		||||
      targetModel.updateMany({[templatePath]: mongoose.Types.ObjectId(templateData._id)},
 | 
			
		||||
        {$rename:
 | 
			
		||||
          changedParameterNames.reduce((s, e) => {s[pathPrefix + e.oldName] = pathPrefix + e.name; return s;}, {})
 | 
			
		||||
        }) .log(req).lean().exec(err => {
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        model(req).findByIdAndUpdate(req.params.id,
 | 
			
		||||
          {$set:
 | 
			
		||||
            changedParameterNames.reduce(
 | 
			
		||||
              (s, e) => {s[`parameters.${e.index}.name`] = e.name; return s;}, {name: template.name}
 | 
			
		||||
            ),
 | 
			
		||||
          },{new: true}).log(req).lean().exec((err, data) => {
 | 
			
		||||
          if (err) next (err);
 | 
			
		||||
          res.json(TemplateValidate.output(data));
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      template.version = templateData.version + 1;  // increase version
 | 
			
		||||
      // save new template, fill with old properties
 | 
			
		||||
      await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {
 | 
			
		||||
        if (err) next (err);
 | 
			
		||||
        db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
 | 
			
		||||
        res.json(TemplateValidate.output(data.toObject()));
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    res.json(TemplateValidate.output(templateData));
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
																							targetModel.updateMany({[templatePath]: mongoose.Types.ObjectId(templateData._id)},
 | 
			
		||||
																												   {$rename:
 | 
			
		||||
																													   changedParameterNames.reduce((s, e) => {s[pathPrefix + e.oldName] = pathPrefix + e.name; return s;}, {})
 | 
			
		||||
																												   }) .log(req).lean().exec(err => {
 | 
			
		||||
																													   if (err) return next(err);
 | 
			
		||||
																													   model(req).findByIdAndUpdate(req.params.id,
 | 
			
		||||
																																					{$set:
 | 
			
		||||
																																						changedParameterNames.reduce(
 | 
			
		||||
																																							(s, e) => {s[`parameters.${e.index}.name`] = e.name; return s;}, {name: template.name}
 | 
			
		||||
																																					),
 | 
			
		||||
																																					},{new: true}).log(req).lean().exec((err, data) => {
 | 
			
		||||
																																						if (err) next (err);
 | 
			
		||||
																																						res.json(TemplateValidate.output(data));
 | 
			
		||||
																																					});
 | 
			
		||||
																												   });
 | 
			
		||||
							}
 | 
			
		||||
							else {
 | 
			
		||||
								template.version = templateData.version + 1;  // increase version
 | 
			
		||||
								// save new template, fill with old properties
 | 
			
		||||
								await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {
 | 
			
		||||
									if (err) next (err);
 | 
			
		||||
									db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
 | 
			
		||||
									res.json(TemplateValidate.output(data.toObject()));
 | 
			
		||||
								});
 | 
			
		||||
							}
 | 
			
		||||
			   }
 | 
			
		||||
			   else {
 | 
			
		||||
				   res.json(TemplateValidate.output(templateData));
 | 
			
		||||
			   }
 | 
			
		||||
		   });
 | 
			
		||||
 | 
			
		||||
router.post('/template/:collection(measurement|condition|material)/new', async (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
		   router.post('/template/:collection(measurement|condition|material)/new', async (req, res, next) => {
 | 
			
		||||
			   if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: template} = TemplateValidate.input(req.body, 'new');
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
			   const {error, value: template} = TemplateValidate.input(req.body, 'new');
 | 
			
		||||
			   if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  template._id = mongoose.Types.ObjectId();  // set reference to itself for first version of template
 | 
			
		||||
  template.first_id = template._id;
 | 
			
		||||
  template.version = 1;  // set template version
 | 
			
		||||
  await new (model(req))(template).save((err, data) => {
 | 
			
		||||
    if (err) next (err);
 | 
			
		||||
    db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
 | 
			
		||||
    res.json(TemplateValidate.output(data.toObject()));
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
			   template._id = mongoose.Types.ObjectId();  // set reference to itself for first version of template
 | 
			
		||||
				   template.first_id = template._id;
 | 
			
		||||
			   template.version = 1;  // set template version
 | 
			
		||||
			   await new (model(req))(template).save((err, data) => {
 | 
			
		||||
				   if (err) next (err);
 | 
			
		||||
				   db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
 | 
			
		||||
				   res.json(TemplateValidate.output(data.toObject()));
 | 
			
		||||
			   });
 | 
			
		||||
		   });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
		   module.exports = router;
 | 
			
		||||
 | 
			
		||||
function model (req) {  // return right template model
 | 
			
		||||
  switch (req.params.collection) {
 | 
			
		||||
    case 'condition': return ConditionTemplateModel
 | 
			
		||||
    case 'measurement': return MeasurementTemplateModel
 | 
			
		||||
    case 'material': return MaterialTemplateModel
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		   function model (req) {  // return right template model
 | 
			
		||||
			   switch (req.params.collection) {
 | 
			
		||||
				   case 'condition': return ConditionTemplateModel
 | 
			
		||||
				   case 'measurement': return MeasurementTemplateModel
 | 
			
		||||
				   case 'material': return MaterialTemplateModel
 | 
			
		||||
			   }
 | 
			
		||||
		   }
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -15,206 +15,206 @@ const router = express.Router();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
router.get('/users', (req, res) => {
 | 
			
		||||
  if (!req.auth(res, ['admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  UserModel.find({}).lean().exec(  (err, data:any) => {
 | 
			
		||||
    // validate all and filter null values from validation errors
 | 
			
		||||
    res.json(_.compact(data.map(e => UserValidate.output(e, 'admin'))));
 | 
			
		||||
  });
 | 
			
		||||
	UserModel.find({}).lean().exec(  (err, data:any) => {
 | 
			
		||||
		// validate all and filter null values from validation errors
 | 
			
		||||
		res.json(_.compact(data.map(e => UserValidate.output(e, 'admin'))));
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
 | 
			
		||||
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
 | 
			
		||||
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const username = getUsername(req, res);
 | 
			
		||||
  if (!username) return;
 | 
			
		||||
  UserModel.findOne({name: username}).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json(UserValidate.output(data));  // validate all and filter null values from validation errors
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
	const username = getUsername(req, res);
 | 
			
		||||
	if (!username) return;
 | 
			
		||||
	UserModel.findOne({name: username}).lean().exec(  (err, data:any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (data) {
 | 
			
		||||
			res.json(UserValidate.output(data));  // validate all and filter null values from validation errors
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
 | 
			
		||||
router.put('/user:username([/](?!key|new|restore).?*|/?)', async (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const username = getUsername(req, res);
 | 
			
		||||
  if (!username) return;
 | 
			
		||||
	const username = getUsername(req, res);
 | 
			
		||||
	if (!username) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: user} = UserValidate.input(req.body, 'change' +
 | 
			
		||||
    (req.authDetails.level === 'admin'? 'admin' : ''));
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	const {error, value: user} = UserValidate.input(req.body, 'change' +
 | 
			
		||||
													(req.authDetails.level === 'admin'? 'admin' : ''));
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  if (user.hasOwnProperty('pass')) {
 | 
			
		||||
    user.pass = bcrypt.hashSync(user.pass, 10);
 | 
			
		||||
  }
 | 
			
		||||
	if (user.hasOwnProperty('pass')) {
 | 
			
		||||
		user.pass = bcrypt.hashSync(user.pass, 10);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // check that user does not already exist if new name was specified
 | 
			
		||||
  if (user.hasOwnProperty('name') && user.name !== username) {
 | 
			
		||||
    if (!await usernameCheck(user.name, res, next)) return;
 | 
			
		||||
  }
 | 
			
		||||
	// check that user does not already exist if new name was specified
 | 
			
		||||
	if (user.hasOwnProperty('name') && user.name !== username) {
 | 
			
		||||
		if (!await usernameCheck(user.name, res, next)) return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  if (user.hasOwnProperty('models')) {
 | 
			
		||||
    if (!await modelsCheck(user.models, res, next)) return;
 | 
			
		||||
  }
 | 
			
		||||
	if (user.hasOwnProperty('models')) {
 | 
			
		||||
		if (!await modelsCheck(user.models, res, next)) return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // get current mail address to compare to given address
 | 
			
		||||
  const oldUserData = await UserModel.findOne({name: username}).lean().exec().catch(err => next(err));
 | 
			
		||||
	// get current mail address to compare to given address
 | 
			
		||||
	const oldUserData = await UserModel.findOne({name: username}).lean().exec().catch(err => next(err));
 | 
			
		||||
 | 
			
		||||
  await UserModel.findOneAndUpdate({name: username}, user, {new: true}).log(req).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      if (data.email !== oldUserData.email) {  // mail address was changed, send notice to old address
 | 
			
		||||
        Mail.send(oldUserData.email, 'Email change in your DeFinMa database account',
 | 
			
		||||
          'Hi, <br><br> Your email address of your DeFinMa account was changed to ' + data.mail +
 | 
			
		||||
          '<br><br>If you actually did this, just delete this email.' +
 | 
			
		||||
          '<br><br>If you did not change your email, someone might be messing around with your account, ' +
 | 
			
		||||
          'so talk to the sysadmin quickly!<br><br>Have a nice day.' +
 | 
			
		||||
          '<br><br>The DeFinMa team');
 | 
			
		||||
      }
 | 
			
		||||
      res.json(UserValidate.output(data));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
	await UserModel.findOneAndUpdate({name: username}, user, {new: true}).log(req).lean().exec(  (err, data:any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (data) {
 | 
			
		||||
			if (data.email !== oldUserData.email) {  // mail address was changed, send notice to old address
 | 
			
		||||
				Mail.send(oldUserData.email, 'Email change in your DeFinMa database account',
 | 
			
		||||
						  'Hi, <br><br> Your email address of your DeFinMa account was changed to ' + data.mail +
 | 
			
		||||
							  '<br><br>If you actually did this, just delete this email.' +
 | 
			
		||||
							  '<br><br>If you did not change your email, someone might be messing around with your account, ' +
 | 
			
		||||
							  'so talk to the sysadmin quickly!<br><br>Have a nice day.' +
 | 
			
		||||
							  '<br><br>The DeFinMa team');
 | 
			
		||||
			}
 | 
			
		||||
			res.json(UserValidate.output(data));
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
 | 
			
		||||
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
 | 
			
		||||
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const username = getUsername(req, res);
 | 
			
		||||
  if (!username) return;
 | 
			
		||||
	const username = getUsername(req, res);
 | 
			
		||||
	if (!username) return;
 | 
			
		||||
 | 
			
		||||
  UserModel.findOneAndUpdate({name: username}, {status: globals.status.del}).log(req).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json({status: 'OK'})
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
	UserModel.findOneAndUpdate({name: username}, {status: globals.status.del}).log(req).lean().exec(  (err, data:any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (data) {
 | 
			
		||||
			res.json({status: 'OK'})
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.put('/user/restore/:username', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  UserModel.findOneAndUpdate({name: req.params.username}, {status: globals.status.new})
 | 
			
		||||
    .log(req).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
	UserModel.findOneAndUpdate({name: req.params.username}, {status: globals.status.new})
 | 
			
		||||
	.log(req).lean().exec((err, data) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
		if (!data) {
 | 
			
		||||
			return res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
		res.json({status: 'OK'});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/user/key', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  UserModel.findOne({name: req.authDetails.username}).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json({key: data.key});
 | 
			
		||||
  });
 | 
			
		||||
	UserModel.findOne({name: req.authDetails.username}).lean().exec(  (err, data:any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		res.json({key: data.key});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/user/new', async (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['admin'], 'basic')) return;
 | 
			
		||||
	if (!req.auth(res, ['admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  // validate input
 | 
			
		||||
  const {error, value: user} = UserValidate.input(req.body, 'new');
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
	// validate input
 | 
			
		||||
	const {error, value: user} = UserValidate.input(req.body, 'new');
 | 
			
		||||
	if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  // check that user does not already exist
 | 
			
		||||
  if (!await usernameCheck(user.name, res, next)) return;
 | 
			
		||||
  if (!await modelsCheck(user.models, res, next)) return;
 | 
			
		||||
	// check that user does not already exist
 | 
			
		||||
	if (!await usernameCheck(user.name, res, next)) return;
 | 
			
		||||
	if (!await modelsCheck(user.models, res, next)) return;
 | 
			
		||||
 | 
			
		||||
  user.key = mongoose.Types.ObjectId();  // use object id as unique API key
 | 
			
		||||
  user.status = globals.status.new;
 | 
			
		||||
  bcrypt.hash(user.pass, 10, (err, hash) => {  // password hashing
 | 
			
		||||
    user.pass = hash;
 | 
			
		||||
    new UserModel(user).save((err, data) => {  // store user
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      db.log(req, 'users', {_id: data._id}, data.toObject());
 | 
			
		||||
      res.json(UserValidate.output(data.toObject()));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	user.key = mongoose.Types.ObjectId();  // use object id as unique API key
 | 
			
		||||
	user.status = globals.status.new;
 | 
			
		||||
	bcrypt.hash(user.pass, 10, (err, hash) => {  // password hashing
 | 
			
		||||
		user.pass = hash;
 | 
			
		||||
		new UserModel(user).save((err, data) => {  // store user
 | 
			
		||||
			if (err) return next(err);
 | 
			
		||||
			db.log(req, 'users', {_id: data._id}, data.toObject());
 | 
			
		||||
			res.json(UserValidate.output(data.toObject()));
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/user/passreset', (req, res, next) => {
 | 
			
		||||
  // check if user/email combo exists
 | 
			
		||||
  UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data.length === 1) {  // it exists
 | 
			
		||||
      const newPass = Math.random().toString(36).substring(2);  // generate temporary password
 | 
			
		||||
      bcrypt.hash(newPass, 10, (err, hash) => {  // password hashing
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
	// check if user/email combo exists
 | 
			
		||||
	UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
 | 
			
		||||
		if (err) return next(err);
 | 
			
		||||
		if (data.length === 1) {  // it exists
 | 
			
		||||
			const newPass = Math.random().toString(36).substring(2);  // generate temporary password
 | 
			
		||||
			bcrypt.hash(newPass, 10, (err, hash) => {  // password hashing
 | 
			
		||||
				if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
        UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}).log(req).exec(err => {  // write new password
 | 
			
		||||
          if (err) return next(err);
 | 
			
		||||
				UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}).log(req).exec(err => {  // write new password
 | 
			
		||||
					if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
          // send email
 | 
			
		||||
          Mail.send(data[0].email, 'Your new password for the DeFinMa database',
 | 
			
		||||
            'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '' +
 | 
			
		||||
            '<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.' +
 | 
			
		||||
            '<br><br>The DeFinMa team', err => {
 | 
			
		||||
            if (err) return next(err);
 | 
			
		||||
            res.json({status: 'OK'});
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
					// send email
 | 
			
		||||
					Mail.send(data[0].email, 'Your new password for the DeFinMa database',
 | 
			
		||||
							  'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '' +
 | 
			
		||||
							  '<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.' +
 | 
			
		||||
						  '<br><br>The DeFinMa team', err => {
 | 
			
		||||
								  if (err) return next(err);
 | 
			
		||||
								  res.json({status: 'OK'});
 | 
			
		||||
							  });
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			res.status(404).json({status: 'Not found'});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
 | 
			
		||||
function getUsername (req, res) {  // returns username or false if action is not allowed
 | 
			
		||||
  req.params.username = req.params[0];  // because of path regex
 | 
			
		||||
  if (req.params.username !== undefined) {  // different username than request user
 | 
			
		||||
    if (!req.auth(res, ['admin'], 'basic')) return false;
 | 
			
		||||
    return req.params.username;
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    return req.authDetails.username;
 | 
			
		||||
  }
 | 
			
		||||
	req.params.username = req.params[0];  // because of path regex
 | 
			
		||||
if (req.params.username !== undefined) {  // different username than request user
 | 
			
		||||
	if (!req.auth(res, ['admin'], 'basic')) return false;
 | 
			
		||||
	return req.params.username;
 | 
			
		||||
}
 | 
			
		||||
else {
 | 
			
		||||
	return req.authDetails.username;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function usernameCheck (name, res, next) {  // check if username is already taken
 | 
			
		||||
  const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
  if (userData instanceof Error) return false;
 | 
			
		||||
  if (userData || UserValidate.isSpecialName(name)) {
 | 
			
		||||
    res.status(400).json({status: 'Username already taken'});
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
	const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
if (userData instanceof Error) return false;
 | 
			
		||||
if (userData || UserValidate.isSpecialName(name)) {
 | 
			
		||||
	res.status(400).json({status: 'Username already taken'});
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function modelsCheck (models, res, next) {  // check if model ids exist, returns false on error
 | 
			
		||||
  let result = true;
 | 
			
		||||
  for (let i in models) {
 | 
			
		||||
    const model = await ModelModel.findOne({'models._id': mongoose.Types.ObjectId(models[i])})
 | 
			
		||||
      .lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
    if(!model) {
 | 
			
		||||
      res.status(400).json({status: 'Invalid model id'});
 | 
			
		||||
      result = false;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
	let result = true;
 | 
			
		||||
for (let i in models) {
 | 
			
		||||
	const model = await ModelModel.findOne({'models._id': mongoose.Types.ObjectId(models[i])})
 | 
			
		||||
	.lean().exec().catch(err => next(err)) as any;
 | 
			
		||||
	if(!model) {
 | 
			
		||||
		res.status(400).json({status: 'Invalid model id'});
 | 
			
		||||
		result = false;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,33 +2,33 @@ import Joi from 'joi';
 | 
			
		||||
import globals from '../../globals';
 | 
			
		||||
 | 
			
		||||
export default class HelpValidate {
 | 
			
		||||
  private static help = {
 | 
			
		||||
    text: Joi.string()
 | 
			
		||||
      .allow('')
 | 
			
		||||
      .max(8192),
 | 
			
		||||
	private static help = {
 | 
			
		||||
		text: Joi.string()
 | 
			
		||||
		.allow('')
 | 
			
		||||
		.max(8192),
 | 
			
		||||
 | 
			
		||||
    level: Joi.string()
 | 
			
		||||
      .valid('none', ...Object.values(globals.levels))
 | 
			
		||||
  }
 | 
			
		||||
		level: Joi.string()
 | 
			
		||||
		.valid('none', ...Object.values(globals.levels))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static input (data) {
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      text: this.help.text.required(),
 | 
			
		||||
      level: this.help.level.required()
 | 
			
		||||
    }).validate(data);
 | 
			
		||||
  }
 | 
			
		||||
	static input (data) {
 | 
			
		||||
		return Joi.object({
 | 
			
		||||
			text: this.help.text.required(),
 | 
			
		||||
			level: this.help.level.required()
 | 
			
		||||
		}).validate(data);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static output (data) {
 | 
			
		||||
    const {value, error} = Joi.object({
 | 
			
		||||
      text: this.help.text,
 | 
			
		||||
      level: this.help.level
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	static output (data) {
 | 
			
		||||
		const {value, error} = Joi.object({
 | 
			
		||||
			text: this.help.text,
 | 
			
		||||
			level: this.help.level
 | 
			
		||||
		}).validate(data, {stripUnknown: true});
 | 
			
		||||
		return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static params(data) {
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      key: Joi.string().min(1).max(128)
 | 
			
		||||
    }).validate(data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static params(data) {
 | 
			
		||||
		return Joi.object({
 | 
			
		||||
			key: Joi.string().min(1).max(128)
 | 
			
		||||
		}).validate(data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,33 @@
 | 
			
		||||
import Joi from 'joi';
 | 
			
		||||
 | 
			
		||||
export default class IdValidate {
 | 
			
		||||
  private static id = Joi.string()
 | 
			
		||||
    .pattern(new RegExp('[0-9a-f]{24}'))
 | 
			
		||||
    .length(24)
 | 
			
		||||
    .messages({'string.pattern.base': 'Invalid object id'});
 | 
			
		||||
	private static id = Joi.string()
 | 
			
		||||
	.pattern(new RegExp('[0-9a-f]{24}'))
 | 
			
		||||
	.length(24)
 | 
			
		||||
	.messages({'string.pattern.base': 'Invalid object id'});
 | 
			
		||||
 | 
			
		||||
  static get () {  // return joi validation
 | 
			
		||||
    return this.id;
 | 
			
		||||
  }
 | 
			
		||||
	static get () {  // return joi validation
 | 
			
		||||
		return this.id;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static valid (id) {  // validate id
 | 
			
		||||
    return this.id.validate(id).error === undefined;
 | 
			
		||||
  }
 | 
			
		||||
	static valid (id) {  // validate id
 | 
			
		||||
		return this.id.validate(id).error === undefined;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static parameter () {  // :id url parameter
 | 
			
		||||
    return ':id([0-9a-f]{24})';
 | 
			
		||||
  }
 | 
			
		||||
	static parameter () {  // :id url parameter
 | 
			
		||||
		return ':id([0-9a-f]{24})';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static stringify (data) {  // convert all ObjectID objects to plain strings
 | 
			
		||||
    Object.keys(data).forEach(key => {
 | 
			
		||||
      // stringify id
 | 
			
		||||
      if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
 | 
			
		||||
        data[key] = data[key].toString();
 | 
			
		||||
      }
 | 
			
		||||
      else if (typeof data[key] === 'object' && data[key] !== null) {  // deeper into recursion
 | 
			
		||||
        data[key] = this.stringify(data[key]);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static stringify (data) {  // convert all ObjectID objects to plain strings
 | 
			
		||||
		Object.keys(data).forEach(key => {
 | 
			
		||||
			// stringify id
 | 
			
		||||
			if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
 | 
			
		||||
				data[key] = data[key].toString();
 | 
			
		||||
			}
 | 
			
		||||
			else if (typeof data[key] === 'object' && data[key] !== null) {  // deeper into recursion
 | 
			
		||||
				data[key] = this.stringify(data[key]);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		return data;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,99 +4,99 @@ import IdValidate from './id';
 | 
			
		||||
import globals from '../../globals';
 | 
			
		||||
 | 
			
		||||
export default class MaterialValidate {  // validate input for material
 | 
			
		||||
  private static material = {
 | 
			
		||||
    name: Joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
	private static material = {
 | 
			
		||||
		name: Joi.string()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    supplier: Joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
		supplier: Joi.string()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    group: Joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
		group: Joi.string()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    properties: Joi.object(),
 | 
			
		||||
		properties: Joi.object(),
 | 
			
		||||
 | 
			
		||||
    numbers: Joi.array()
 | 
			
		||||
      .items(
 | 
			
		||||
        Joi.string()
 | 
			
		||||
          .max(64)
 | 
			
		||||
      ),
 | 
			
		||||
		numbers: Joi.array()
 | 
			
		||||
		.items(
 | 
			
		||||
			Joi.string()
 | 
			
		||||
			.max(64)
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
    status: Joi.string()
 | 
			
		||||
      .valid(...Object.values(globals.status))
 | 
			
		||||
  };
 | 
			
		||||
		status: Joi.string()
 | 
			
		||||
		.valid(...Object.values(globals.status))
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
    if (param === 'new') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        name: this.material.name.required(),
 | 
			
		||||
        supplier: this.material.supplier.required(),
 | 
			
		||||
        group: this.material.group.required(),
 | 
			
		||||
        properties: this.material.properties.required(),
 | 
			
		||||
        numbers: this.material.numbers.required()
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'change') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        name: this.material.name,
 | 
			
		||||
        supplier: this.material.supplier,
 | 
			
		||||
        group: this.material.group,
 | 
			
		||||
        properties: this.material.properties,
 | 
			
		||||
        numbers: this.material.numbers
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
		if (param === 'new') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				name: this.material.name.required(),
 | 
			
		||||
				supplier: this.material.supplier.required(),
 | 
			
		||||
				group: this.material.group.required(),
 | 
			
		||||
				properties: this.material.properties.required(),
 | 
			
		||||
				numbers: this.material.numbers.required()
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else if (param === 'change') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				name: this.material.name,
 | 
			
		||||
				supplier: this.material.supplier,
 | 
			
		||||
				group: this.material.group,
 | 
			
		||||
				properties: this.material.properties,
 | 
			
		||||
				numbers: this.material.numbers
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static output (data, status = false) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    data.group = data.group_id.name;
 | 
			
		||||
    data.supplier = data.supplier_id.name;
 | 
			
		||||
    const validate: any = {
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.material.name,
 | 
			
		||||
      supplier: this.material.supplier,
 | 
			
		||||
      group: this.material.group,
 | 
			
		||||
      properties: this.material.properties,
 | 
			
		||||
      numbers: this.material.numbers
 | 
			
		||||
    };
 | 
			
		||||
    if (status) {
 | 
			
		||||
      validate.status = this.material.status;
 | 
			
		||||
    }
 | 
			
		||||
    const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	static output (data, status = false) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
		data = IdValidate.stringify(data);
 | 
			
		||||
	data.group = data.group_id.name;
 | 
			
		||||
	data.supplier = data.supplier_id.name;
 | 
			
		||||
	const validate: any = {
 | 
			
		||||
		_id: IdValidate.get(),
 | 
			
		||||
		name: this.material.name,
 | 
			
		||||
		supplier: this.material.supplier,
 | 
			
		||||
		group: this.material.group,
 | 
			
		||||
		properties: this.material.properties,
 | 
			
		||||
		numbers: this.material.numbers
 | 
			
		||||
	};
 | 
			
		||||
	if (status) {
 | 
			
		||||
		validate.status = this.material.status;
 | 
			
		||||
	}
 | 
			
		||||
	const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true});
 | 
			
		||||
	return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static outputGroups (data)  {// validate groups output and strip unwanted properties, returns null if not valid
 | 
			
		||||
    const {value, error} = this.material.group.validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	static outputGroups (data)  {// validate groups output and strip unwanted properties, returns null if not valid
 | 
			
		||||
		const {value, error} = this.material.group.validate(data, {stripUnknown: true});
 | 
			
		||||
	return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static outputSuppliers (data)  {// validate suppliers output and strip unwanted properties, returns null if not valid
 | 
			
		||||
    const {value, error} = this.material.supplier.validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	static outputSuppliers (data)  {// validate suppliers output and strip unwanted properties, returns null if not valid
 | 
			
		||||
		const {value, error} = this.material.supplier.validate(data, {stripUnknown: true});
 | 
			
		||||
	return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static outputV() {  // return output validator
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.material.name,
 | 
			
		||||
      supplier: this.material.supplier,
 | 
			
		||||
      group: this.material.group,
 | 
			
		||||
      properties: this.material.properties,
 | 
			
		||||
      numbers: this.material.numbers
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
	static outputV() {  // return output validator
 | 
			
		||||
		return Joi.object({
 | 
			
		||||
			_id: IdValidate.get(),
 | 
			
		||||
			name: this.material.name,
 | 
			
		||||
			supplier: this.material.supplier,
 | 
			
		||||
			group: this.material.group,
 | 
			
		||||
			properties: this.material.properties,
 | 
			
		||||
			numbers: this.material.numbers
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static query (data, dev = false) {
 | 
			
		||||
    const acceptedStatuses = [globals.status.val, globals.status.new];
 | 
			
		||||
    if (dev) {  // dev and admin can also access deleted samples
 | 
			
		||||
      acceptedStatuses.push(globals.status.del)
 | 
			
		||||
    }
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val])
 | 
			
		||||
    }).validate(data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static query (data, dev = false) {
 | 
			
		||||
		const acceptedStatuses = [globals.status.val, globals.status.new];
 | 
			
		||||
		if (dev) {  // dev and admin can also access deleted samples
 | 
			
		||||
			acceptedStatuses.push(globals.status.del)
 | 
			
		||||
		}
 | 
			
		||||
		return Joi.object({
 | 
			
		||||
			status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val])
 | 
			
		||||
		}).validate(data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,64 +4,64 @@ import IdValidate from './id';
 | 
			
		||||
import globals from '../../globals';
 | 
			
		||||
 | 
			
		||||
export default class MeasurementValidate {
 | 
			
		||||
  private static measurement = {
 | 
			
		||||
    values: Joi.object()
 | 
			
		||||
      .pattern(/.*/, Joi.alternatives()
 | 
			
		||||
        .try(
 | 
			
		||||
          Joi.string().max(128),
 | 
			
		||||
          Joi.number(),
 | 
			
		||||
          Joi.boolean(),
 | 
			
		||||
          Joi.array().items(Joi.array().items(Joi.number())),  // for spectra
 | 
			
		||||
          Joi.array()
 | 
			
		||||
        )
 | 
			
		||||
        .allow(null)
 | 
			
		||||
      )
 | 
			
		||||
  };
 | 
			
		||||
	private static measurement = {
 | 
			
		||||
		values: Joi.object()
 | 
			
		||||
		.pattern(/.*/, Joi.alternatives()
 | 
			
		||||
.try(
 | 
			
		||||
	Joi.string().max(128),
 | 
			
		||||
	Joi.number(),
 | 
			
		||||
	Joi.boolean(),
 | 
			
		||||
	Joi.array().items(Joi.array().items(Joi.number())),  // for spectra
 | 
			
		||||
		Joi.array()
 | 
			
		||||
)
 | 
			
		||||
.allow(null)
 | 
			
		||||
				)
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
    if (param === 'new') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        sample_id: IdValidate.get().required(),
 | 
			
		||||
        values: this.measurement.values.required(),
 | 
			
		||||
        measurement_template: IdValidate.get().required()
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'change') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        values: this.measurement.values
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
		if (param === 'new') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				sample_id: IdValidate.get().required(),
 | 
			
		||||
				values: this.measurement.values.required(),
 | 
			
		||||
				measurement_template: IdValidate.get().required()
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else if (param === 'change') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				values: this.measurement.values
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
  static output (data, req, status = false) {
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    // spectral data not allowed for read/write users
 | 
			
		||||
    if (['dev', 'admin'].indexOf(req.authDetails.level) < 0 && data.values[globals.spectrum.dpt]) {
 | 
			
		||||
      delete data.values[globals.spectrum.dpt];
 | 
			
		||||
    }
 | 
			
		||||
    const validation: any = {
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      sample_id: IdValidate.get(),
 | 
			
		||||
      values: this.measurement.values,
 | 
			
		||||
      measurement_template: IdValidate.get()
 | 
			
		||||
    };
 | 
			
		||||
    if (status) {
 | 
			
		||||
      validation.status = Joi.string().valid(...Object.values(globals.status));
 | 
			
		||||
    }
 | 
			
		||||
    const {value, error} = Joi.object(validation).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	// validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
	static output (data, req, status = false) {
 | 
			
		||||
		data = IdValidate.stringify(data);
 | 
			
		||||
		// spectral data not allowed for read/write users
 | 
			
		||||
		if (['dev', 'admin'].indexOf(req.authDetails.level) < 0 && data.values[globals.spectrum.dpt]) {
 | 
			
		||||
			delete data.values[globals.spectrum.dpt];
 | 
			
		||||
		}
 | 
			
		||||
		const validation: any = {
 | 
			
		||||
			_id: IdValidate.get(),
 | 
			
		||||
			sample_id: IdValidate.get(),
 | 
			
		||||
			values: this.measurement.values,
 | 
			
		||||
			measurement_template: IdValidate.get()
 | 
			
		||||
		};
 | 
			
		||||
		if (status) {
 | 
			
		||||
			validation.status = Joi.string().valid(...Object.values(globals.status));
 | 
			
		||||
		}
 | 
			
		||||
		const {value, error} = Joi.object(validation).validate(data, {stripUnknown: true});
 | 
			
		||||
		return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static outputV() {  // return output validator
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      sample_id: IdValidate.get(),
 | 
			
		||||
      values: this.measurement.values,
 | 
			
		||||
      measurement_template: IdValidate.get()
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static outputV() {  // return output validator
 | 
			
		||||
		return Joi.object({
 | 
			
		||||
			_id: IdValidate.get(),
 | 
			
		||||
			sample_id: IdValidate.get(),
 | 
			
		||||
			values: this.measurement.values,
 | 
			
		||||
			measurement_template: IdValidate.get()
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,40 +3,40 @@ import IdValidate from './id';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default class ModelValidate {  // validate input for model
 | 
			
		||||
  private static model = {
 | 
			
		||||
    group: Joi.string()
 | 
			
		||||
      .disallow('file')
 | 
			
		||||
      .max(128),
 | 
			
		||||
	private static model = {
 | 
			
		||||
		group: Joi.string()
 | 
			
		||||
		.disallow('file')
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    model: Joi.object({
 | 
			
		||||
        name: Joi.string()
 | 
			
		||||
          .max(128)
 | 
			
		||||
          .required(),
 | 
			
		||||
		model: Joi.object({
 | 
			
		||||
			name: Joi.string()
 | 
			
		||||
			.max(128)
 | 
			
		||||
			.required(),
 | 
			
		||||
 | 
			
		||||
        url: Joi.string()
 | 
			
		||||
          .uri()
 | 
			
		||||
          .max(512)
 | 
			
		||||
          .required()
 | 
			
		||||
      })
 | 
			
		||||
  };
 | 
			
		||||
			url: Joi.string()
 | 
			
		||||
			.uri()
 | 
			
		||||
			.max(512)
 | 
			
		||||
			.required()
 | 
			
		||||
		})
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  static input (data) {  // validate input
 | 
			
		||||
    return this.model.model.required().validate(data);
 | 
			
		||||
  }
 | 
			
		||||
	static input (data) {  // validate input
 | 
			
		||||
		return this.model.model.required().validate(data);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const {value, error} = Joi.object({
 | 
			
		||||
      group: this.model.group,
 | 
			
		||||
      models: Joi.array().items(this.model.model.append({_id: IdValidate.get()}))
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
		data = IdValidate.stringify(data);
 | 
			
		||||
	const {value, error} = Joi.object({
 | 
			
		||||
		group: this.model.group,
 | 
			
		||||
		models: Joi.array().items(this.model.model.append({_id: IdValidate.get()}))
 | 
			
		||||
	}).validate(data, {stripUnknown: true});
 | 
			
		||||
	return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static fileOutput (data) {
 | 
			
		||||
    return {
 | 
			
		||||
      name: data.name,
 | 
			
		||||
      size: data.data.length
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static fileOutput (data) {
 | 
			
		||||
		return {
 | 
			
		||||
			name: data.name,
 | 
			
		||||
			size: data.data.length
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
import Joi from 'joi';
 | 
			
		||||
 | 
			
		||||
export default class NoteFieldValidate {
 | 
			
		||||
  private static note_field = {
 | 
			
		||||
    name: Joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
	private static note_field = {
 | 
			
		||||
		name: Joi.string()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    qty: Joi.number()
 | 
			
		||||
  };
 | 
			
		||||
		qty: Joi.number()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
    const {value, error} = Joi.object({
 | 
			
		||||
      name: this.note_field.name,
 | 
			
		||||
      qty: this.note_field.qty
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
		const {value, error} = Joi.object({
 | 
			
		||||
			name: this.note_field.name,
 | 
			
		||||
			qty: this.note_field.qty
 | 
			
		||||
		}).validate(data, {stripUnknown: true});
 | 
			
		||||
		return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,42 +1,42 @@
 | 
			
		||||
import Joi from 'joi';
 | 
			
		||||
 | 
			
		||||
export default class ParametersValidate {
 | 
			
		||||
  // data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed)
 | 
			
		||||
  static input (data, parameters, param) {
 | 
			
		||||
    let joiObject = {};
 | 
			
		||||
    parameters.forEach(parameter => {
 | 
			
		||||
      switch (parameter.range.type) {
 | 
			
		||||
        case 'number': joiObject[parameter.name] = Joi.number();
 | 
			
		||||
          break;
 | 
			
		||||
        case 'boolean': joiObject[parameter.name] = Joi.boolean();
 | 
			
		||||
          break;
 | 
			
		||||
        case 'array': joiObject[parameter.name] = Joi.array();
 | 
			
		||||
          break;
 | 
			
		||||
        case 'string': joiObject[parameter.name] = Joi.string().max(128);
 | 
			
		||||
          break;     //  min or max implicitly define the value to be a number
 | 
			
		||||
        default: if (parameter.range.hasOwnProperty('min') || parameter.range.hasOwnProperty('max')) {
 | 
			
		||||
          joiObject[parameter.name] = Joi.number();
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            joiObject[parameter.name] = Joi.string().max(128);
 | 
			
		||||
          }
 | 
			
		||||
      }
 | 
			
		||||
      if (parameter.range.hasOwnProperty('min')) {
 | 
			
		||||
        joiObject[parameter.name] = joiObject[parameter.name].min(parameter.range.min)
 | 
			
		||||
      }
 | 
			
		||||
      if (parameter.range.hasOwnProperty('max')) {
 | 
			
		||||
        joiObject[parameter.name] = joiObject[parameter.name].max(parameter.range.max)
 | 
			
		||||
      }
 | 
			
		||||
      if (parameter.range.hasOwnProperty('values')) {
 | 
			
		||||
        joiObject[parameter.name] = joiObject[parameter.name].valid(...parameter.range.values);
 | 
			
		||||
      }
 | 
			
		||||
      if (parameter.range.hasOwnProperty('required') && parameter.range.required) {
 | 
			
		||||
        joiObject[parameter.name] = joiObject[parameter.name].required();
 | 
			
		||||
      }
 | 
			
		||||
      if (param === 'null') {
 | 
			
		||||
        joiObject[parameter.name] = joiObject[parameter.name].allow(null)
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return Joi.object(joiObject).validate(data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	// data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed)
 | 
			
		||||
	static input (data, parameters, param) {
 | 
			
		||||
		let joiObject = {};
 | 
			
		||||
		parameters.forEach(parameter => {
 | 
			
		||||
			switch (parameter.range.type) {
 | 
			
		||||
				case 'number': joiObject[parameter.name] = Joi.number();
 | 
			
		||||
				break;
 | 
			
		||||
				case 'boolean': joiObject[parameter.name] = Joi.boolean();
 | 
			
		||||
				break;
 | 
			
		||||
				case 'array': joiObject[parameter.name] = Joi.array();
 | 
			
		||||
				break;
 | 
			
		||||
				case 'string': joiObject[parameter.name] = Joi.string().max(128);
 | 
			
		||||
				break;     //  min or max implicitly define the value to be a number
 | 
			
		||||
				default: if (parameter.range.hasOwnProperty('min') || parameter.range.hasOwnProperty('max')) {
 | 
			
		||||
					joiObject[parameter.name] = Joi.number();
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					joiObject[parameter.name] = Joi.string().max(128);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (parameter.range.hasOwnProperty('min')) {
 | 
			
		||||
				joiObject[parameter.name] = joiObject[parameter.name].min(parameter.range.min)
 | 
			
		||||
			}
 | 
			
		||||
			if (parameter.range.hasOwnProperty('max')) {
 | 
			
		||||
				joiObject[parameter.name] = joiObject[parameter.name].max(parameter.range.max)
 | 
			
		||||
			}
 | 
			
		||||
			if (parameter.range.hasOwnProperty('values')) {
 | 
			
		||||
				joiObject[parameter.name] = joiObject[parameter.name].valid(...parameter.range.values);
 | 
			
		||||
			}
 | 
			
		||||
			if (parameter.range.hasOwnProperty('required') && parameter.range.required) {
 | 
			
		||||
				joiObject[parameter.name] = joiObject[parameter.name].required();
 | 
			
		||||
			}
 | 
			
		||||
			if (param === 'null') {
 | 
			
		||||
				joiObject[parameter.name] = joiObject[parameter.name].allow(null)
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		return Joi.object(joiObject).validate(data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
// respond with 400 and include error details from the joi validation
 | 
			
		||||
 | 
			
		||||
export default function res400 (error, res) {
 | 
			
		||||
  res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
 | 
			
		||||
}
 | 
			
		||||
	res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,50 +2,50 @@ import Joi from 'joi';
 | 
			
		||||
import IdValidate from './id';
 | 
			
		||||
 | 
			
		||||
export default class RootValidate {  // validate input for root methods
 | 
			
		||||
  private static changelog = {
 | 
			
		||||
    timestamp: Joi.date()
 | 
			
		||||
      .iso()
 | 
			
		||||
      .min('1970-01-01T00:00:00.000Z'),
 | 
			
		||||
	private static changelog = {
 | 
			
		||||
		timestamp: Joi.date()
 | 
			
		||||
		.iso()
 | 
			
		||||
		.min('1970-01-01T00:00:00.000Z'),
 | 
			
		||||
 | 
			
		||||
    page: Joi.number()
 | 
			
		||||
      .integer()
 | 
			
		||||
      .min(0)
 | 
			
		||||
      .default(0),
 | 
			
		||||
		page: Joi.number()
 | 
			
		||||
		.integer()
 | 
			
		||||
		.min(0)
 | 
			
		||||
		.default(0),
 | 
			
		||||
 | 
			
		||||
    pagesize: Joi.number()
 | 
			
		||||
      .integer()
 | 
			
		||||
      .min(0)
 | 
			
		||||
      .default(25),
 | 
			
		||||
		pagesize: Joi.number()
 | 
			
		||||
		.integer()
 | 
			
		||||
		.min(0)
 | 
			
		||||
		.default(25),
 | 
			
		||||
 | 
			
		||||
    action: Joi.string(),
 | 
			
		||||
		action: Joi.string(),
 | 
			
		||||
 | 
			
		||||
    collection: Joi.string(),
 | 
			
		||||
		collection: Joi.string(),
 | 
			
		||||
 | 
			
		||||
    conditions: Joi.object(),
 | 
			
		||||
		conditions: Joi.object(),
 | 
			
		||||
 | 
			
		||||
    data: Joi.object()
 | 
			
		||||
  };
 | 
			
		||||
		data: Joi.object()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  static changelogParams (data) {
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      id: IdValidate.get(),
 | 
			
		||||
      page: this.changelog.page,
 | 
			
		||||
      pagesize: this.changelog.pagesize
 | 
			
		||||
    }).validate(data);
 | 
			
		||||
  }
 | 
			
		||||
	static changelogParams (data) {
 | 
			
		||||
		return Joi.object({
 | 
			
		||||
			id: IdValidate.get(),
 | 
			
		||||
			page: this.changelog.page,
 | 
			
		||||
			pagesize: this.changelog.pagesize
 | 
			
		||||
		}).validate(data);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static changelogOutput (data) {
 | 
			
		||||
    data.date = data._id.getTimestamp();
 | 
			
		||||
    data.collection = data.collection_name;
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const {value, error} = Joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      date: this.changelog.timestamp,
 | 
			
		||||
      action: this.changelog.action,
 | 
			
		||||
      collection: this.changelog.collection,
 | 
			
		||||
      conditions: this.changelog.conditions,
 | 
			
		||||
      data: this.changelog.data,
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static changelogOutput (data) {
 | 
			
		||||
		data.date = data._id.getTimestamp();
 | 
			
		||||
		data.collection = data.collection_name;
 | 
			
		||||
		data = IdValidate.stringify(data);
 | 
			
		||||
		const {value, error} = Joi.object({
 | 
			
		||||
			_id: IdValidate.get(),
 | 
			
		||||
			date: this.changelog.timestamp,
 | 
			
		||||
			action: this.changelog.action,
 | 
			
		||||
			collection: this.changelog.collection,
 | 
			
		||||
			conditions: this.changelog.conditions,
 | 
			
		||||
			data: this.changelog.data,
 | 
			
		||||
		}).validate(data, {stripUnknown: true});
 | 
			
		||||
		return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,273 +8,273 @@ import globals from '../../globals';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default class SampleValidate {
 | 
			
		||||
  private static sample = {
 | 
			
		||||
    number: Joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
	private static sample = {
 | 
			
		||||
		number: Joi.string()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    color: Joi.string()
 | 
			
		||||
      .max(128)
 | 
			
		||||
      .allow(''),
 | 
			
		||||
		color: Joi.string()
 | 
			
		||||
		.max(128)
 | 
			
		||||
		.allow(''),
 | 
			
		||||
 | 
			
		||||
    type: Joi.string()
 | 
			
		||||
      .valid('as-delivered/raw', 'processed'),
 | 
			
		||||
		type: Joi.string()
 | 
			
		||||
		.valid('as-delivered/raw', 'processed'),
 | 
			
		||||
 | 
			
		||||
    batch: Joi.string()
 | 
			
		||||
      .max(128)
 | 
			
		||||
      .allow(''),
 | 
			
		||||
		batch: Joi.string()
 | 
			
		||||
		.max(128)
 | 
			
		||||
		.allow(''),
 | 
			
		||||
 | 
			
		||||
    condition: Joi.object(),
 | 
			
		||||
		condition: Joi.object(),
 | 
			
		||||
 | 
			
		||||
    notes: Joi.object({
 | 
			
		||||
      comment: Joi.string()
 | 
			
		||||
        .max(512)
 | 
			
		||||
        .allow('')
 | 
			
		||||
        .allow(null),
 | 
			
		||||
		notes: Joi.object({
 | 
			
		||||
			comment: Joi.string()
 | 
			
		||||
			.max(512)
 | 
			
		||||
			.allow('')
 | 
			
		||||
			.allow(null),
 | 
			
		||||
 | 
			
		||||
      sample_references: Joi.array()
 | 
			
		||||
        .items(Joi.object({
 | 
			
		||||
          sample_id: IdValidate.get(),
 | 
			
		||||
			sample_references: Joi.array()
 | 
			
		||||
			.items(Joi.object({
 | 
			
		||||
				sample_id: IdValidate.get(),
 | 
			
		||||
 | 
			
		||||
          relation: Joi.string()
 | 
			
		||||
            .max(128)
 | 
			
		||||
        })),
 | 
			
		||||
				relation: Joi.string()
 | 
			
		||||
				.max(128)
 | 
			
		||||
			})),
 | 
			
		||||
 | 
			
		||||
      custom_fields: Joi.object()
 | 
			
		||||
        .pattern(/.*/, Joi.alternatives()
 | 
			
		||||
          .try(
 | 
			
		||||
            Joi.string().max(128),
 | 
			
		||||
            Joi.number(),
 | 
			
		||||
            Joi.boolean(),
 | 
			
		||||
            Joi.date()
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
    }),
 | 
			
		||||
			custom_fields: Joi.object()
 | 
			
		||||
			.pattern(/.*/, Joi.alternatives()
 | 
			
		||||
.try(
 | 
			
		||||
	Joi.string().max(128),
 | 
			
		||||
	Joi.number(),
 | 
			
		||||
	Joi.boolean(),
 | 
			
		||||
	Joi.date()
 | 
			
		||||
)
 | 
			
		||||
					)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
    added: Joi.date()
 | 
			
		||||
      .iso()
 | 
			
		||||
      .min('1970-01-01T00:00:00.000Z'),
 | 
			
		||||
		added: Joi.date()
 | 
			
		||||
		.iso()
 | 
			
		||||
		.min('1970-01-01T00:00:00.000Z'),
 | 
			
		||||
 | 
			
		||||
    status: Joi.string()
 | 
			
		||||
      .valid(...Object.values(globals.status))
 | 
			
		||||
  };
 | 
			
		||||
		status: Joi.string()
 | 
			
		||||
		.valid(...Object.values(globals.status))
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  static readonly sampleKeys = [  // keys which can be found in the sample directly
 | 
			
		||||
    '_id',
 | 
			
		||||
    'color',
 | 
			
		||||
    'number',
 | 
			
		||||
    'type',
 | 
			
		||||
    'batch',
 | 
			
		||||
    'added',
 | 
			
		||||
    'condition',
 | 
			
		||||
    'material_id',
 | 
			
		||||
    'note_id',
 | 
			
		||||
    'user_id'
 | 
			
		||||
  ];
 | 
			
		||||
	static readonly sampleKeys = [  // keys which can be found in the sample directly
 | 
			
		||||
		'_id',
 | 
			
		||||
		'color',
 | 
			
		||||
		'number',
 | 
			
		||||
		'type',
 | 
			
		||||
		'batch',
 | 
			
		||||
		'added',
 | 
			
		||||
		'condition',
 | 
			
		||||
		'material_id',
 | 
			
		||||
		'note_id',
 | 
			
		||||
		'user_id'
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
  private static sortKeys = [
 | 
			
		||||
    '_id',
 | 
			
		||||
    'color',
 | 
			
		||||
    'number',
 | 
			
		||||
    'type',
 | 
			
		||||
    'batch',
 | 
			
		||||
    'added',
 | 
			
		||||
    'status',
 | 
			
		||||
    'notes.comment',
 | 
			
		||||
    'material.name',
 | 
			
		||||
    'material.supplier',
 | 
			
		||||
    'material.group',
 | 
			
		||||
    'material.properties.*',
 | 
			
		||||
    'condition.*',
 | 
			
		||||
    `measurements.(?!${globals.spectrum.spectrum}.${globals.spectrum.dpt})*`
 | 
			
		||||
  ];
 | 
			
		||||
	private static sortKeys = [
 | 
			
		||||
		'_id',
 | 
			
		||||
		'color',
 | 
			
		||||
		'number',
 | 
			
		||||
		'type',
 | 
			
		||||
		'batch',
 | 
			
		||||
		'added',
 | 
			
		||||
		'status',
 | 
			
		||||
		'notes.comment',
 | 
			
		||||
		'material.name',
 | 
			
		||||
		'material.supplier',
 | 
			
		||||
		'material.group',
 | 
			
		||||
		'material.properties.*',
 | 
			
		||||
		'condition.*',
 | 
			
		||||
		`measurements.(?!${globals.spectrum.spectrum}.${globals.spectrum.dpt})*`
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
  private static fieldKeys = [
 | 
			
		||||
    ...SampleValidate.sortKeys,
 | 
			
		||||
    'condition',
 | 
			
		||||
    'notes',
 | 
			
		||||
    'material_id',
 | 
			
		||||
    'material',
 | 
			
		||||
    'note_id',
 | 
			
		||||
    'user_id',
 | 
			
		||||
    'material._id',
 | 
			
		||||
    'material.numbers',
 | 
			
		||||
    'measurements',
 | 
			
		||||
    `measurements.${globals.spectrum.spectrum}.${globals.spectrum.dpt}`,
 | 
			
		||||
  ];
 | 
			
		||||
	private static fieldKeys = [
 | 
			
		||||
		...SampleValidate.sortKeys,
 | 
			
		||||
		'condition',
 | 
			
		||||
		'notes',
 | 
			
		||||
		'material_id',
 | 
			
		||||
		'material',
 | 
			
		||||
		'note_id',
 | 
			
		||||
		'user_id',
 | 
			
		||||
		'material._id',
 | 
			
		||||
		'material.numbers',
 | 
			
		||||
		'measurements',
 | 
			
		||||
		`measurements.${globals.spectrum.spectrum}.${globals.spectrum.dpt}`,
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
    if (param === 'new') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        color: this.sample.color.required(),
 | 
			
		||||
        type: this.sample.type.required(),
 | 
			
		||||
        batch: this.sample.batch.required(),
 | 
			
		||||
        condition: this.sample.condition.required(),
 | 
			
		||||
        material_id: IdValidate.get().required(),
 | 
			
		||||
        notes: this.sample.notes.required()
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'change') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        color: this.sample.color,
 | 
			
		||||
        type: this.sample.type,
 | 
			
		||||
        batch: this.sample.batch,
 | 
			
		||||
        condition: this.sample.condition,
 | 
			
		||||
        material_id: IdValidate.get(),
 | 
			
		||||
        notes: this.sample.notes,
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'new-admin') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        number: this.sample.number,
 | 
			
		||||
        color: this.sample.color.required(),
 | 
			
		||||
        type: this.sample.type.required(),
 | 
			
		||||
        batch: this.sample.batch.required(),
 | 
			
		||||
        condition: this.sample.condition.required(),
 | 
			
		||||
        material_id: IdValidate.get().required(),
 | 
			
		||||
        notes: this.sample.notes.required()
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
		if (param === 'new') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				color: this.sample.color.required(),
 | 
			
		||||
				type: this.sample.type.required(),
 | 
			
		||||
				batch: this.sample.batch.required(),
 | 
			
		||||
				condition: this.sample.condition.required(),
 | 
			
		||||
				material_id: IdValidate.get().required(),
 | 
			
		||||
				notes: this.sample.notes.required()
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else if (param === 'change') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				color: this.sample.color,
 | 
			
		||||
				type: this.sample.type,
 | 
			
		||||
				batch: this.sample.batch,
 | 
			
		||||
				condition: this.sample.condition,
 | 
			
		||||
				material_id: IdValidate.get(),
 | 
			
		||||
				notes: this.sample.notes,
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else if (param === 'new-admin') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				number: this.sample.number,
 | 
			
		||||
				color: this.sample.color.required(),
 | 
			
		||||
				type: this.sample.type.required(),
 | 
			
		||||
				batch: this.sample.batch.required(),
 | 
			
		||||
				condition: this.sample.condition.required(),
 | 
			
		||||
				material_id: IdValidate.get().required(),
 | 
			
		||||
				notes: this.sample.notes.required()
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
  static output (data, param = 'refs+added', additionalParams = []) {
 | 
			
		||||
    if (param === 'refs+added') {
 | 
			
		||||
      param = 'refs';
 | 
			
		||||
      data.added = data._id.getTimestamp();
 | 
			
		||||
    }
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    let joiObject;
 | 
			
		||||
    if (param === 'refs') {
 | 
			
		||||
      joiObject = {
 | 
			
		||||
        _id: IdValidate.get(),
 | 
			
		||||
        number: this.sample.number,
 | 
			
		||||
        color: this.sample.color,
 | 
			
		||||
        type: this.sample.type,
 | 
			
		||||
        batch: this.sample.batch,
 | 
			
		||||
        condition: this.sample.condition,
 | 
			
		||||
        material_id: IdValidate.get(),
 | 
			
		||||
        material: MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')}),
 | 
			
		||||
        note_id: IdValidate.get().allow(null),
 | 
			
		||||
        notes: this.sample.notes,
 | 
			
		||||
        user_id: IdValidate.get(),
 | 
			
		||||
        added: this.sample.added,
 | 
			
		||||
        status: this.sample.status
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    else if(param === 'details') {
 | 
			
		||||
      joiObject = {
 | 
			
		||||
        _id: IdValidate.get(),
 | 
			
		||||
        number: this.sample.number,
 | 
			
		||||
        color: this.sample.color,
 | 
			
		||||
        type: this.sample.type,
 | 
			
		||||
        batch: this.sample.batch,
 | 
			
		||||
        condition: this.sample.condition,
 | 
			
		||||
        material: MaterialValidate.outputV(),
 | 
			
		||||
        measurements: Joi.array().items(MeasurementValidate.outputV()),
 | 
			
		||||
        notes: this.sample.notes,
 | 
			
		||||
        user: UserValidate.username(),
 | 
			
		||||
        status: this.sample.status
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    additionalParams.forEach(param => {
 | 
			
		||||
      joiObject[param] = Joi.any();
 | 
			
		||||
    });
 | 
			
		||||
    const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	// validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
	static output (data, param = 'refs+added', additionalParams = []) {
 | 
			
		||||
		if (param === 'refs+added') {
 | 
			
		||||
			param = 'refs';
 | 
			
		||||
			data.added = data._id.getTimestamp();
 | 
			
		||||
		}
 | 
			
		||||
		data = IdValidate.stringify(data);
 | 
			
		||||
		let joiObject;
 | 
			
		||||
		if (param === 'refs') {
 | 
			
		||||
			joiObject = {
 | 
			
		||||
				_id: IdValidate.get(),
 | 
			
		||||
				number: this.sample.number,
 | 
			
		||||
				color: this.sample.color,
 | 
			
		||||
				type: this.sample.type,
 | 
			
		||||
				batch: this.sample.batch,
 | 
			
		||||
				condition: this.sample.condition,
 | 
			
		||||
				material_id: IdValidate.get(),
 | 
			
		||||
				material: MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')}),
 | 
			
		||||
				note_id: IdValidate.get().allow(null),
 | 
			
		||||
				notes: this.sample.notes,
 | 
			
		||||
				user_id: IdValidate.get(),
 | 
			
		||||
				added: this.sample.added,
 | 
			
		||||
				status: this.sample.status
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
		else if(param === 'details') {
 | 
			
		||||
			joiObject = {
 | 
			
		||||
				_id: IdValidate.get(),
 | 
			
		||||
				number: this.sample.number,
 | 
			
		||||
				color: this.sample.color,
 | 
			
		||||
				type: this.sample.type,
 | 
			
		||||
				batch: this.sample.batch,
 | 
			
		||||
				condition: this.sample.condition,
 | 
			
		||||
				material: MaterialValidate.outputV(),
 | 
			
		||||
				measurements: Joi.array().items(MeasurementValidate.outputV()),
 | 
			
		||||
				notes: this.sample.notes,
 | 
			
		||||
				user: UserValidate.username(),
 | 
			
		||||
				status: this.sample.status
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
		additionalParams.forEach(param => {
 | 
			
		||||
			joiObject[param] = Joi.any();
 | 
			
		||||
		});
 | 
			
		||||
		const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
 | 
			
		||||
		return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static query (data, dev = false) {
 | 
			
		||||
    if (data.filters && data.filters.length) {
 | 
			
		||||
      const filterValidation = Joi.array().items(Joi.string()).validate(data.filters);
 | 
			
		||||
      if (filterValidation.error) return filterValidation;
 | 
			
		||||
      try {
 | 
			
		||||
        for (let i in data.filters) {
 | 
			
		||||
          try {
 | 
			
		||||
            data.filters[i] = decodeURIComponent(data.filters[i]);
 | 
			
		||||
          }
 | 
			
		||||
          catch (ignore) {}
 | 
			
		||||
          data.filters[i] = JSON.parse(data.filters[i]);
 | 
			
		||||
          data.filters[i].values = data.filters[i].values.map(e => {  // validate filter values
 | 
			
		||||
            if (e === null) {  // null values are always allowed
 | 
			
		||||
              return null;
 | 
			
		||||
            }
 | 
			
		||||
            let validator;
 | 
			
		||||
            let field = data.filters[i].field;
 | 
			
		||||
            if (/material\./.test(field)) {  // select right validation model
 | 
			
		||||
              validator = MaterialValidate.outputV().append({
 | 
			
		||||
                number: Joi.string().max(128).allow(''),
 | 
			
		||||
                properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128).allow(''))
 | 
			
		||||
              });
 | 
			
		||||
              field = field.replace('material.', '').split('.')[0];
 | 
			
		||||
            }
 | 
			
		||||
            else if (/measurements\./.test(field) || /condition\./.test(field)) {
 | 
			
		||||
              validator = Joi.object({
 | 
			
		||||
                value: Joi.alternatives()
 | 
			
		||||
                  .try(
 | 
			
		||||
                    Joi.number(),
 | 
			
		||||
                    Joi.string().max(128).allow(''),
 | 
			
		||||
                    Joi.boolean(),
 | 
			
		||||
                    Joi.array()
 | 
			
		||||
                  )
 | 
			
		||||
                  .allow(null)
 | 
			
		||||
              });
 | 
			
		||||
              field = 'value';
 | 
			
		||||
            }
 | 
			
		||||
            else if (field === 'measurements') {
 | 
			
		||||
              validator = Joi.object({
 | 
			
		||||
                value: Joi.object({}).allow(null).disallow({})
 | 
			
		||||
              });
 | 
			
		||||
              field = 'value';
 | 
			
		||||
            }
 | 
			
		||||
            else if (field === 'notes.comment') {
 | 
			
		||||
              field = 'comment';
 | 
			
		||||
              validator = this.sample.notes
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              validator = Joi.object(this.sample);
 | 
			
		||||
            }
 | 
			
		||||
            const {value, error} = validator.validate({[field]: e});
 | 
			
		||||
            if (error) throw error;  // reject invalid values
 | 
			
		||||
            return value[field];
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      catch (err) {
 | 
			
		||||
        return {error: {details: [{message: 'Invalid JSON string for filter parameter'}]}, value: null}
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const acceptedStatuses = [globals.status.val, globals.status.new];
 | 
			
		||||
    if (dev) {  // dev and admin can also access deleted samples
 | 
			
		||||
      acceptedStatuses.push(globals.status.del)
 | 
			
		||||
    }
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val]),
 | 
			
		||||
      'from-id': IdValidate.get(),
 | 
			
		||||
      'to-page': Joi.number().integer(),
 | 
			
		||||
      'page-size': Joi.number().integer().min(1),
 | 
			
		||||
      sort: Joi.string().pattern(
 | 
			
		||||
        new RegExp('^(' + this.sortKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')-(asc|desc)$', 'm')
 | 
			
		||||
      ).default('_id-asc'),
 | 
			
		||||
      output: Joi.string().valid('json', 'flatten', 'csv').default('json'),
 | 
			
		||||
      fields: Joi.array().items(Joi.string().pattern(
 | 
			
		||||
        new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
 | 
			
		||||
      )).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added'])
 | 
			
		||||
        .messages({'string.pattern.base': 'Invalid field name'}),
 | 
			
		||||
      filters: Joi.array().items(Joi.object({
 | 
			
		||||
        mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin', 'stringin'),
 | 
			
		||||
        field: Joi.string().pattern(
 | 
			
		||||
          new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
 | 
			
		||||
        ).messages({'string.pattern.base': 'Invalid filter field name'}),
 | 
			
		||||
        values: Joi.array().items(Joi.alternatives().try(
 | 
			
		||||
          Joi.string().max(128).allow(''), Joi.number(), Joi.boolean(), Joi.date().iso(), Joi.object(), null
 | 
			
		||||
        )).min(1)
 | 
			
		||||
      })).default([])
 | 
			
		||||
    }).with('to-page', 'page-size').validate(data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static query (data, dev = false) {
 | 
			
		||||
		if (data.filters && data.filters.length) {
 | 
			
		||||
			const filterValidation = Joi.array().items(Joi.string()).validate(data.filters);
 | 
			
		||||
			if (filterValidation.error) return filterValidation;
 | 
			
		||||
			try {
 | 
			
		||||
				for (let i in data.filters) {
 | 
			
		||||
					try {
 | 
			
		||||
						data.filters[i] = decodeURIComponent(data.filters[i]);
 | 
			
		||||
					}
 | 
			
		||||
					catch (ignore) {}
 | 
			
		||||
					data.filters[i] = JSON.parse(data.filters[i]);
 | 
			
		||||
					data.filters[i].values = data.filters[i].values.map(e => {  // validate filter values
 | 
			
		||||
						if (e === null) {  // null values are always allowed
 | 
			
		||||
							return null;
 | 
			
		||||
						}
 | 
			
		||||
						let validator;
 | 
			
		||||
						let field = data.filters[i].field;
 | 
			
		||||
						if (/material\./.test(field)) {  // select right validation model
 | 
			
		||||
							validator = MaterialValidate.outputV().append({
 | 
			
		||||
								number: Joi.string().max(128).allow(''),
 | 
			
		||||
								properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128).allow(''))
 | 
			
		||||
							});
 | 
			
		||||
							field = field.replace('material.', '').split('.')[0];
 | 
			
		||||
						}
 | 
			
		||||
						else if (/measurements\./.test(field) || /condition\./.test(field)) {
 | 
			
		||||
							validator = Joi.object({
 | 
			
		||||
								value: Joi.alternatives()
 | 
			
		||||
								.try(
 | 
			
		||||
									Joi.number(),
 | 
			
		||||
									Joi.string().max(128).allow(''),
 | 
			
		||||
									Joi.boolean(),
 | 
			
		||||
									Joi.array()
 | 
			
		||||
								)
 | 
			
		||||
								.allow(null)
 | 
			
		||||
							});
 | 
			
		||||
							field = 'value';
 | 
			
		||||
						}
 | 
			
		||||
						else if (field === 'measurements') {
 | 
			
		||||
							validator = Joi.object({
 | 
			
		||||
								value: Joi.object({}).allow(null).disallow({})
 | 
			
		||||
							});
 | 
			
		||||
							field = 'value';
 | 
			
		||||
						}
 | 
			
		||||
						else if (field === 'notes.comment') {
 | 
			
		||||
							field = 'comment';
 | 
			
		||||
							validator = this.sample.notes
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
							validator = Joi.object(this.sample);
 | 
			
		||||
						}
 | 
			
		||||
						const {value, error} = validator.validate({[field]: e});
 | 
			
		||||
						if (error) throw error;  // reject invalid values
 | 
			
		||||
						return value[field];
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			catch (err) {
 | 
			
		||||
				return {error: {details: [{message: 'Invalid JSON string for filter parameter'}]}, value: null}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const acceptedStatuses = [globals.status.val, globals.status.new];
 | 
			
		||||
		if (dev) {  // dev and admin can also access deleted samples
 | 
			
		||||
			acceptedStatuses.push(globals.status.del)
 | 
			
		||||
		}
 | 
			
		||||
		return Joi.object({
 | 
			
		||||
			status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val]),
 | 
			
		||||
			'from-id': IdValidate.get(),
 | 
			
		||||
			'to-page': Joi.number().integer(),
 | 
			
		||||
			'page-size': Joi.number().integer().min(1),
 | 
			
		||||
			sort: Joi.string().pattern(
 | 
			
		||||
				new RegExp('^(' + this.sortKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')-(asc|desc)$', 'm')
 | 
			
		||||
			).default('_id-asc'),
 | 
			
		||||
			output: Joi.string().valid('json', 'flatten', 'csv').default('json'),
 | 
			
		||||
			fields: Joi.array().items(Joi.string().pattern(
 | 
			
		||||
				new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
 | 
			
		||||
			)).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added'])
 | 
			
		||||
			.messages({'string.pattern.base': 'Invalid field name'}),
 | 
			
		||||
			filters: Joi.array().items(Joi.object({
 | 
			
		||||
				mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin', 'stringin'),
 | 
			
		||||
				field: Joi.string().pattern(
 | 
			
		||||
					new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
 | 
			
		||||
				).messages({'string.pattern.base': 'Invalid filter field name'}),
 | 
			
		||||
				values: Joi.array().items(Joi.alternatives().try(
 | 
			
		||||
					Joi.string().max(128).allow(''), Joi.number(), Joi.boolean(), Joi.date().iso(), Joi.object(), null
 | 
			
		||||
				)).min(1)
 | 
			
		||||
			})).default([])
 | 
			
		||||
		}).with('to-page', 'page-size').validate(data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,70 +2,70 @@ import Joi from 'joi';
 | 
			
		||||
import IdValidate from './id';
 | 
			
		||||
 | 
			
		||||
export default class TemplateValidate {
 | 
			
		||||
  private static template = {
 | 
			
		||||
    name: Joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
	private static template = {
 | 
			
		||||
		name: Joi.string()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    version: Joi.number()
 | 
			
		||||
      .min(1),
 | 
			
		||||
		version: Joi.number()
 | 
			
		||||
		.min(1),
 | 
			
		||||
 | 
			
		||||
    parameters: Joi.array()
 | 
			
		||||
      .items(
 | 
			
		||||
        Joi.object({
 | 
			
		||||
          name: Joi.string()
 | 
			
		||||
            .max(128)
 | 
			
		||||
            .invalid('condition_template', 'material_template')
 | 
			
		||||
            .pattern(/^[^.]+$/)
 | 
			
		||||
            .required()
 | 
			
		||||
            .messages({'string.pattern.base': 'name must not contain a dot'}),
 | 
			
		||||
		parameters: Joi.array()
 | 
			
		||||
		.items(
 | 
			
		||||
			Joi.object({
 | 
			
		||||
				name: Joi.string()
 | 
			
		||||
				.max(128)
 | 
			
		||||
				.invalid('condition_template', 'material_template')
 | 
			
		||||
				.pattern(/^[^.]+$/)
 | 
			
		||||
				.required()
 | 
			
		||||
				.messages({'string.pattern.base': 'name must not contain a dot'}),
 | 
			
		||||
 | 
			
		||||
          range: Joi.object({
 | 
			
		||||
            values: Joi.array()
 | 
			
		||||
              .min(1),
 | 
			
		||||
				range: Joi.object({
 | 
			
		||||
					values: Joi.array()
 | 
			
		||||
					.min(1),
 | 
			
		||||
 | 
			
		||||
            min: Joi.number(),
 | 
			
		||||
					min: Joi.number(),
 | 
			
		||||
 | 
			
		||||
            max: Joi.number(),
 | 
			
		||||
					max: Joi.number(),
 | 
			
		||||
 | 
			
		||||
            type: Joi.string()
 | 
			
		||||
              .valid('string', 'number', 'boolean', 'array'),
 | 
			
		||||
					type: Joi.string()
 | 
			
		||||
					.valid('string', 'number', 'boolean', 'array'),
 | 
			
		||||
 | 
			
		||||
            required: Joi.boolean()
 | 
			
		||||
          })
 | 
			
		||||
            .oxor('values', 'min')
 | 
			
		||||
            .oxor('values', 'max')
 | 
			
		||||
            .required()
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
  };
 | 
			
		||||
					required: Joi.boolean()
 | 
			
		||||
				})
 | 
			
		||||
				.oxor('values', 'min')
 | 
			
		||||
				.oxor('values', 'max')
 | 
			
		||||
				.required()
 | 
			
		||||
			})
 | 
			
		||||
		)
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
    if (param === 'new') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        name: this.template.name.required(),
 | 
			
		||||
        parameters: this.template.parameters.required()
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'change') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        name: this.template.name,
 | 
			
		||||
        parameters: this.template.parameters
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
		if (param === 'new') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				name: this.template.name.required(),
 | 
			
		||||
				parameters: this.template.parameters.required()
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else if (param === 'change') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				name: this.template.name,
 | 
			
		||||
				parameters: this.template.parameters
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const {value, error} = Joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.template.name,
 | 
			
		||||
      version: this.template.version,
 | 
			
		||||
      first_id: IdValidate.get(),
 | 
			
		||||
      parameters: this.template.parameters
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
		data = IdValidate.stringify(data);
 | 
			
		||||
	const {value, error} = Joi.object({
 | 
			
		||||
		_id: IdValidate.get(),
 | 
			
		||||
		name: this.template.name,
 | 
			
		||||
		version: this.template.version,
 | 
			
		||||
		first_id: IdValidate.get(),
 | 
			
		||||
		parameters: this.template.parameters
 | 
			
		||||
	}).validate(data, {stripUnknown: true});
 | 
			
		||||
	return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,104 +4,104 @@ import globals from '../../globals';
 | 
			
		||||
import IdValidate from './id';
 | 
			
		||||
 | 
			
		||||
export default class UserValidate {  // validate input for user
 | 
			
		||||
  private static user = {
 | 
			
		||||
    name: Joi.string()
 | 
			
		||||
      .lowercase()
 | 
			
		||||
      .pattern(new RegExp('^[a-z0-9-_.]+$'))
 | 
			
		||||
      .max(128)
 | 
			
		||||
      .messages({'string.pattern.base': 'name must only contain a-z0-9_.'}),
 | 
			
		||||
	private static user = {
 | 
			
		||||
		name: Joi.string()
 | 
			
		||||
		.lowercase()
 | 
			
		||||
		.pattern(new RegExp('^[a-z0-9-_.]+$'))
 | 
			
		||||
		.max(128)
 | 
			
		||||
		.messages({'string.pattern.base': 'name must only contain a-z0-9_.'}),
 | 
			
		||||
 | 
			
		||||
    email: Joi.string()
 | 
			
		||||
      .email({minDomainSegments: 2})
 | 
			
		||||
      .lowercase()
 | 
			
		||||
      .max(128),
 | 
			
		||||
		email: Joi.string()
 | 
			
		||||
		.email({minDomainSegments: 2})
 | 
			
		||||
		.lowercase()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    pass: Joi.string()
 | 
			
		||||
      .min(8)
 | 
			
		||||
      .max(128),
 | 
			
		||||
		pass: Joi.string()
 | 
			
		||||
		.min(8)
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    level: Joi.string()
 | 
			
		||||
      .valid(...Object.values(globals.levels)),
 | 
			
		||||
		level: Joi.string()
 | 
			
		||||
		.valid(...Object.values(globals.levels)),
 | 
			
		||||
 | 
			
		||||
    location: Joi.string()
 | 
			
		||||
      .alphanum()
 | 
			
		||||
      .max(128),
 | 
			
		||||
		location: Joi.string()
 | 
			
		||||
		.alphanum()
 | 
			
		||||
		.max(128),
 | 
			
		||||
 | 
			
		||||
    devices: Joi.array()
 | 
			
		||||
      .items(Joi.string()
 | 
			
		||||
        .allow('')
 | 
			
		||||
        .max(128)
 | 
			
		||||
      ),
 | 
			
		||||
		devices: Joi.array()
 | 
			
		||||
		.items(Joi.string()
 | 
			
		||||
			   .allow('')
 | 
			
		||||
			   .max(128)
 | 
			
		||||
			  ),
 | 
			
		||||
 | 
			
		||||
    models: Joi.array()
 | 
			
		||||
      .items(IdValidate.get()),
 | 
			
		||||
			  models: Joi.array()
 | 
			
		||||
			  .items(IdValidate.get()),
 | 
			
		||||
 | 
			
		||||
    status: Joi.string()
 | 
			
		||||
      .valid(...Object.values(globals.status))
 | 
			
		||||
  };
 | 
			
		||||
			  status: Joi.string()
 | 
			
		||||
			  .valid(...Object.values(globals.status))
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  private static specialUsernames: string[] = ['admin', 'user', 'key', 'new', 'passreset'];  // names a user cannot take
 | 
			
		||||
	private static specialUsernames: string[] = ['admin', 'user', 'key', 'new', 'passreset'];  // names a user cannot take
 | 
			
		||||
 | 
			
		||||
  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
    if (param === 'new') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        name: this.user.name.required(),
 | 
			
		||||
        email: this.user.email.required(),
 | 
			
		||||
        pass: this.user.pass.required(),
 | 
			
		||||
        level: this.user.level.required(),
 | 
			
		||||
        location: this.user.location.required(),
 | 
			
		||||
        devices: this.user.devices.required(),
 | 
			
		||||
        models: this.user.models.required()
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'change') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        name: this.user.name,
 | 
			
		||||
        email: this.user.email,
 | 
			
		||||
        pass: this.user.pass,
 | 
			
		||||
        location: this.user.location,
 | 
			
		||||
        devices: this.user.devices
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'changeadmin') {
 | 
			
		||||
      return Joi.object({
 | 
			
		||||
        name: this.user.name,
 | 
			
		||||
        email: this.user.email,
 | 
			
		||||
        pass: this.user.pass,
 | 
			
		||||
        level: this.user.level,
 | 
			
		||||
        location: this.user.location,
 | 
			
		||||
        devices: this.user.devices,
 | 
			
		||||
        models: this.user.models
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return {error: 'No parameter specified!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
			
		||||
		if (param === 'new') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				name: this.user.name.required(),
 | 
			
		||||
				email: this.user.email.required(),
 | 
			
		||||
				pass: this.user.pass.required(),
 | 
			
		||||
				level: this.user.level.required(),
 | 
			
		||||
				location: this.user.location.required(),
 | 
			
		||||
				devices: this.user.devices.required(),
 | 
			
		||||
				models: this.user.models.required()
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else if (param === 'change') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				name: this.user.name,
 | 
			
		||||
				email: this.user.email,
 | 
			
		||||
				pass: this.user.pass,
 | 
			
		||||
				location: this.user.location,
 | 
			
		||||
				devices: this.user.devices
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else if (param === 'changeadmin') {
 | 
			
		||||
			return Joi.object({
 | 
			
		||||
				name: this.user.name,
 | 
			
		||||
				email: this.user.email,
 | 
			
		||||
				pass: this.user.pass,
 | 
			
		||||
				level: this.user.level,
 | 
			
		||||
				location: this.user.location,
 | 
			
		||||
				devices: this.user.devices,
 | 
			
		||||
				models: this.user.models
 | 
			
		||||
			}).validate(data);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			return {error: 'No parameter specified!', value: {}};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static output (data, param = '') {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const validate: {[key: string]: object} = {
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.user.name,
 | 
			
		||||
      email: this.user.email,
 | 
			
		||||
      level: this.user.level,
 | 
			
		||||
      location: this.user.location,
 | 
			
		||||
      devices: this.user.devices,
 | 
			
		||||
      models: this.user.models
 | 
			
		||||
    }
 | 
			
		||||
    if (param === 'admin') {
 | 
			
		||||
      validate.status = this.user.status;
 | 
			
		||||
    }
 | 
			
		||||
    const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
	static output (data, param = '') {  // validate output and strip unwanted properties, returns null if not valid
 | 
			
		||||
		data = IdValidate.stringify(data);
 | 
			
		||||
	const validate: {[key: string]: object} = {
 | 
			
		||||
		_id: IdValidate.get(),
 | 
			
		||||
		name: this.user.name,
 | 
			
		||||
		email: this.user.email,
 | 
			
		||||
		level: this.user.level,
 | 
			
		||||
		location: this.user.location,
 | 
			
		||||
		devices: this.user.devices,
 | 
			
		||||
		models: this.user.models
 | 
			
		||||
	}
 | 
			
		||||
	if (param === 'admin') {
 | 
			
		||||
		validate.status = this.user.status;
 | 
			
		||||
	}
 | 
			
		||||
	const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true});
 | 
			
		||||
	return error !== undefined? null : value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static isSpecialName (name) {  // true if name belongs to special names
 | 
			
		||||
    return this.specialUsernames.indexOf(name) > -1;
 | 
			
		||||
  }
 | 
			
		||||
	static isSpecialName (name) {  // true if name belongs to special names
 | 
			
		||||
		return this.specialUsernames.indexOf(name) > -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static username() {
 | 
			
		||||
    return this.user.name;
 | 
			
		||||
  }
 | 
			
		||||
	static username() {
 | 
			
		||||
		return this.user.name;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,141 +7,141 @@ import IdValidate from '../routes/validate/id';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default class TestHelper {
 | 
			
		||||
  public static auth = {  // test user credentials
 | 
			
		||||
    admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'},
 | 
			
		||||
    janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'},
 | 
			
		||||
    user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'},
 | 
			
		||||
    johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'},
 | 
			
		||||
    customer: {pass: 'Xyz890*)', key: '000000000000000000001005', id: '000000000000000000000005'}
 | 
			
		||||
  }
 | 
			
		||||
	public static auth = {  // test user credentials
 | 
			
		||||
		admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'},
 | 
			
		||||
		janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'},
 | 
			
		||||
		user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'},
 | 
			
		||||
		johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'},
 | 
			
		||||
		customer: {pass: 'Xyz890*)', key: '000000000000000000001005', id: '000000000000000000000005'}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  public static res = {  // default responses
 | 
			
		||||
    400: {status: 'Bad request'},
 | 
			
		||||
    401: {status: 'Unauthorized'},
 | 
			
		||||
    403: {status: 'Forbidden'},
 | 
			
		||||
    404: {status: 'Not found'},
 | 
			
		||||
    500: {status: 'Internal server error'}
 | 
			
		||||
  }
 | 
			
		||||
	public static res = {  // default responses
 | 
			
		||||
		400: {status: 'Bad request'},
 | 
			
		||||
		401: {status: 'Unauthorized'},
 | 
			
		||||
		403: {status: 'Forbidden'},
 | 
			
		||||
		404: {status: 'Not found'},
 | 
			
		||||
		500: {status: 'Internal server error'}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static before (done) {
 | 
			
		||||
    process.env.port = '2999';
 | 
			
		||||
    process.env.NODE_ENV = 'test';
 | 
			
		||||
    db.connect('test', done);
 | 
			
		||||
  }
 | 
			
		||||
	static before (done) {
 | 
			
		||||
		process.env.port = '2999';
 | 
			
		||||
		process.env.NODE_ENV = 'test';
 | 
			
		||||
		db.connect('test', done);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static beforeEach (server, done) {
 | 
			
		||||
    // delete cached server code except models as these are needed in the testing files as well
 | 
			
		||||
    Object.keys(require.cache).filter(e => /API\\dist\\(?!(models|db|test))/.test(e)).forEach(key => {
 | 
			
		||||
      delete require.cache[key];  // prevent loading from cache
 | 
			
		||||
    });
 | 
			
		||||
    server = require('../index');
 | 
			
		||||
    db.drop(err => {  // reset database
 | 
			
		||||
      if (err) return done(err);
 | 
			
		||||
      db.loadJson(require('./db.json'), done);
 | 
			
		||||
    });
 | 
			
		||||
    return server
 | 
			
		||||
  }
 | 
			
		||||
	static beforeEach (server, done) {
 | 
			
		||||
		// delete cached server code except models as these are needed in the testing files as well
 | 
			
		||||
		Object.keys(require.cache).filter(e => /API\\dist\\(?!(models|db|test))/.test(e)).forEach(key => {
 | 
			
		||||
			delete require.cache[key];  // prevent loading from cache
 | 
			
		||||
		});
 | 
			
		||||
		server = require('../index');
 | 
			
		||||
		db.drop(err => {  // reset database
 | 
			
		||||
			if (err) return done(err);
 | 
			
		||||
			db.loadJson(require('./db.json'), done);
 | 
			
		||||
		});
 | 
			
		||||
		return server
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  // options in form: {method, url, contentType, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res,
 | 
			
		||||
  // default (set to false if you want to dismiss default .end handling)}
 | 
			
		||||
  static request (server, done, options) {
 | 
			
		||||
    let st = supertest(server);
 | 
			
		||||
    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) {  // resolve API key
 | 
			
		||||
      options.url += '?key=' +
 | 
			
		||||
        (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
 | 
			
		||||
    }
 | 
			
		||||
    switch (options.method) {  // http method
 | 
			
		||||
      case 'get':
 | 
			
		||||
        st = st.get(options.url)
 | 
			
		||||
        break;
 | 
			
		||||
      case 'post':
 | 
			
		||||
        st = st.post(options.url)
 | 
			
		||||
        break;
 | 
			
		||||
      case 'put':
 | 
			
		||||
        st = st.put(options.url)
 | 
			
		||||
        break;
 | 
			
		||||
      case 'delete':
 | 
			
		||||
        st = st.delete(options.url)
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    if (options.hasOwnProperty('reqType')) {  // request body
 | 
			
		||||
      st = st.type(options.reqType);
 | 
			
		||||
    }
 | 
			
		||||
    if (options.hasOwnProperty('req')) {  // request body
 | 
			
		||||
      st = st.send(options.req);
 | 
			
		||||
    }
 | 
			
		||||
    if (options.hasOwnProperty('reqContentType')) {  // request body
 | 
			
		||||
      st = st.set('Content-Type', options.reqContentType);
 | 
			
		||||
    }
 | 
			
		||||
    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) {  // resolve basic auth
 | 
			
		||||
      if (this.auth.hasOwnProperty(options.auth.basic)) {
 | 
			
		||||
        st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        st = st.auth(options.auth.basic.name, options.auth.basic.pass)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (options.hasOwnProperty('contentType')) {
 | 
			
		||||
      st = st.expect('Content-type', options.contentType).expect(options.httpStatus);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      st = st.expect('Content-type', /json/).expect(options.httpStatus);
 | 
			
		||||
    }
 | 
			
		||||
    if (options.hasOwnProperty('res')) {  // evaluate result
 | 
			
		||||
      return st.end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        should(res.body).be.eql(options.res);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) {  // evaluate default results
 | 
			
		||||
      return st.end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        should(res.body).be.eql(this.res[options.httpStatus]);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)}
 | 
			
		||||
    else if (options.hasOwnProperty('log')) {
 | 
			
		||||
      return st.end(err => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0)
 | 
			
		||||
          .lean().exec((err, data) => {  // latest entry
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.only.keys('_id', 'action', 'collection_name', 'conditions', 'data', 'user_id', '__v');
 | 
			
		||||
          should(data).have.property('action', options.method.toUpperCase() + ' ' + options.url);
 | 
			
		||||
          should(data).have.property('collection_name', options.log.collection);
 | 
			
		||||
          if (options.log.hasOwnProperty('data')) {
 | 
			
		||||
            should(data).have.property('data', options.log.data);
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            const ignore = ['_id', '__v'];
 | 
			
		||||
            if (options.log.hasOwnProperty('dataIgn')) {
 | 
			
		||||
              ignore.push(...options.log.dataIgn);
 | 
			
		||||
            }
 | 
			
		||||
            let tmp = options.req ? options.req : {};
 | 
			
		||||
            if (options.log.hasOwnProperty('dataAdd')) {
 | 
			
		||||
              _.assign(tmp, options.log.dataAdd)
 | 
			
		||||
            }
 | 
			
		||||
            should(IdValidate.stringify(_.omit(data.data, ignore))).be.eql(_.omit(tmp, ignore));
 | 
			
		||||
          }
 | 
			
		||||
          if (data.user_id) {
 | 
			
		||||
            should(data.user_id.toString()).be.eql(this.auth[options.auth.basic].id);
 | 
			
		||||
          }
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    else {  // return object to do .end() manually
 | 
			
		||||
      return st;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	// options in form: {method, url, contentType, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res,
 | 
			
		||||
	// default (set to false if you want to dismiss default .end handling)}
 | 
			
		||||
	static request (server, done, options) {
 | 
			
		||||
		let st = supertest(server);
 | 
			
		||||
		if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) {  // resolve API key
 | 
			
		||||
			options.url += '?key=' +
 | 
			
		||||
				(this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
 | 
			
		||||
		}
 | 
			
		||||
		switch (options.method) {  // http method
 | 
			
		||||
			case 'get':
 | 
			
		||||
				st = st.get(options.url)
 | 
			
		||||
			break;
 | 
			
		||||
			case 'post':
 | 
			
		||||
				st = st.post(options.url)
 | 
			
		||||
			break;
 | 
			
		||||
			case 'put':
 | 
			
		||||
				st = st.put(options.url)
 | 
			
		||||
			break;
 | 
			
		||||
			case 'delete':
 | 
			
		||||
				st = st.delete(options.url)
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		if (options.hasOwnProperty('reqType')) {  // request body
 | 
			
		||||
			st = st.type(options.reqType);
 | 
			
		||||
		}
 | 
			
		||||
		if (options.hasOwnProperty('req')) {  // request body
 | 
			
		||||
			st = st.send(options.req);
 | 
			
		||||
		}
 | 
			
		||||
		if (options.hasOwnProperty('reqContentType')) {  // request body
 | 
			
		||||
			st = st.set('Content-Type', options.reqContentType);
 | 
			
		||||
		}
 | 
			
		||||
		if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) {  // resolve basic auth
 | 
			
		||||
			if (this.auth.hasOwnProperty(options.auth.basic)) {
 | 
			
		||||
				st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				st = st.auth(options.auth.basic.name, options.auth.basic.pass)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (options.hasOwnProperty('contentType')) {
 | 
			
		||||
			st = st.expect('Content-type', options.contentType).expect(options.httpStatus);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			st = st.expect('Content-type', /json/).expect(options.httpStatus);
 | 
			
		||||
		}
 | 
			
		||||
		if (options.hasOwnProperty('res')) {  // evaluate result
 | 
			
		||||
			return st.end((err, res) => {
 | 
			
		||||
				if (err) return done (err);
 | 
			
		||||
				should(res.body).be.eql(options.res);
 | 
			
		||||
				done();
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) {  // evaluate default results
 | 
			
		||||
			return st.end((err, res) => {
 | 
			
		||||
				if (err) return done (err);
 | 
			
		||||
				should(res.body).be.eql(this.res[options.httpStatus]);
 | 
			
		||||
				done();
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		// check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)}
 | 
			
		||||
		else if (options.hasOwnProperty('log')) {
 | 
			
		||||
			return st.end(err => {
 | 
			
		||||
				if (err) return done (err);
 | 
			
		||||
				ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0)
 | 
			
		||||
				.lean().exec((err, data) => {  // latest entry
 | 
			
		||||
					if (err) return done(err);
 | 
			
		||||
					should(data).have.only.keys('_id', 'action', 'collection_name', 'conditions', 'data', 'user_id', '__v');
 | 
			
		||||
					should(data).have.property('action', options.method.toUpperCase() + ' ' + options.url);
 | 
			
		||||
					should(data).have.property('collection_name', options.log.collection);
 | 
			
		||||
					if (options.log.hasOwnProperty('data')) {
 | 
			
		||||
						should(data).have.property('data', options.log.data);
 | 
			
		||||
					}
 | 
			
		||||
					else {
 | 
			
		||||
						const ignore = ['_id', '__v'];
 | 
			
		||||
						if (options.log.hasOwnProperty('dataIgn')) {
 | 
			
		||||
							ignore.push(...options.log.dataIgn);
 | 
			
		||||
						}
 | 
			
		||||
						let tmp = options.req ? options.req : {};
 | 
			
		||||
						if (options.log.hasOwnProperty('dataAdd')) {
 | 
			
		||||
							_.assign(tmp, options.log.dataAdd)
 | 
			
		||||
						}
 | 
			
		||||
						should(IdValidate.stringify(_.omit(data.data, ignore))).be.eql(_.omit(tmp, ignore));
 | 
			
		||||
					}
 | 
			
		||||
					if (data.user_id) {
 | 
			
		||||
						should(data.user_id.toString()).be.eql(this.auth[options.auth.basic].id);
 | 
			
		||||
					}
 | 
			
		||||
					done();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		else {  // return object to do .end() manually
 | 
			
		||||
			return st;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static afterEach (server, done) {
 | 
			
		||||
    server.close(done);
 | 
			
		||||
  }
 | 
			
		||||
	static afterEach (server, done) {
 | 
			
		||||
		server.close(done);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  static after(done) {
 | 
			
		||||
    db.disconnect(done);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
	static after(done) {
 | 
			
		||||
		db.disconnect(done);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@ import db from '../db';
 | 
			
		||||
// script to load test db into dev db for a clean start
 | 
			
		||||
 | 
			
		||||
db.connect('dev', () => {
 | 
			
		||||
  console.info('dropping data...');
 | 
			
		||||
  db.drop(() => {  // reset database
 | 
			
		||||
    console.info('loading data...');
 | 
			
		||||
    db.loadJson(require('./db.json'), () => {
 | 
			
		||||
      console.info('done');
 | 
			
		||||
      process.exit(0);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
	console.info('dropping data...');
 | 
			
		||||
	db.drop(() => {  // reset database
 | 
			
		||||
		console.info('loading data...');
 | 
			
		||||
		db.loadJson(require('./db.json'), () => {
 | 
			
		||||
			console.info('done');
 | 
			
		||||
			process.exit(0);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user