Archived
2

Properly indent all source files

This commit is contained in:
Kai S. K. Engelbart 2021-01-25 12:57:29 +01:00
parent 0e37956bdb
commit 7c5b6ec605
51 changed files with 9380 additions and 9380 deletions

4
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "dfop-api", "name": "definma-api",
"version": "1.0.0", "version": "0.9.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -9,10 +9,10 @@
/* =================== USAGE =================== /* =================== USAGE ===================
import * as express from "express"; import * as express from "express";
let app = express(); let app = express();
=============================================== */ =============================================== */
/// <reference types="express-serve-static-core" /> /// <reference types="express-serve-static-core" />
/// <reference types="serve-static" /> /// <reference types="serve-static" />
@ -28,96 +28,96 @@ import * as qs from "qs";
declare function e(): core.Express; declare function e(): core.Express;
declare namespace e { declare namespace e {
/** /**
* This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on * This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on
* body-parser. * body-parser.
* @since 4.16.0 * @since 4.16.0
*/ */
let json: typeof bodyParser.json; let json: typeof bodyParser.json;
/** /**
* This is a built-in middleware function in Express. It parses incoming requests with Buffer payloads and is based * This is a built-in middleware function in Express. It parses incoming requests with Buffer payloads and is based
* on body-parser. * on body-parser.
* @since 4.17.0 * @since 4.17.0
*/ */
let raw: typeof bodyParser.raw; 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 * This is a built-in middleware function in Express. It parses incoming requests with text payloads and is based on
* body-parser. * body-parser.
* @since 4.17.0 * @since 4.17.0
*/ */
let text: typeof bodyParser.text; let text: typeof bodyParser.text;
/** /**
* These are the exposed prototypes. * These are the exposed prototypes.
*/ */
let application: Application; let application: Application;
let request: Request; let request: Request;
let response: Response; let response: Response;
/** /**
* This is a built-in middleware function in Express. It serves static files and is based on serve-static. * This is a built-in middleware function in Express. It serves static files and is based on serve-static.
*/ */
let static: typeof serveStatic; let static: typeof serveStatic;
/** /**
* This is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is * This is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is
* based on body-parser. * based on body-parser.
* @since 4.16.0 * @since 4.16.0
*/ */
let urlencoded: typeof bodyParser.urlencoded; let urlencoded: typeof bodyParser.urlencoded;
/** /**
* This is a built-in middleware function in Express. It parses incoming request query parameters. * 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 query(options: qs.IParseOptions | typeof qs.parse): Handler;
export function Router(options?: RouterOptions): core.Router; export function Router(options?: RouterOptions): core.Router;
interface RouterOptions { interface RouterOptions {
/** /**
* Enable case sensitivity. * Enable case sensitivity.
*/ */
caseSensitive?: boolean; caseSensitive?: boolean;
/** /**
* Preserve the req.params values from the parent router. * Preserve the req.params values from the parent router.
* If the parent and the child have conflicting param names, the childs value take precedence. * If the parent and the child have conflicting param names, the childs value take precedence.
* *
* @default false * @default false
* @since 4.5.0 * @since 4.5.0
*/ */
mergeParams?: boolean; mergeParams?: boolean;
/** /**
* Enable strict routing. * Enable strict routing.
*/ */
strict?: boolean; strict?: boolean;
} }
interface Application extends core.Application { } interface Application extends core.Application { }
interface CookieOptions extends core.CookieOptions { } interface CookieOptions extends core.CookieOptions { }
interface Errback extends core.Errback { } interface Errback extends core.Errback { }
interface ErrorRequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, interface ErrorRequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any,
ReqQuery = core.Query> ReqQuery = core.Query>
extends core.ErrorRequestHandler<P, ResBody, ReqBody, ReqQuery> { } extends core.ErrorRequestHandler<P, ResBody, ReqBody, ReqQuery> { }
interface Express extends core.Express { } interface Express extends core.Express { }
interface Handler extends core.Handler { } interface Handler extends core.Handler { }
interface IRoute extends core.IRoute { } interface IRoute extends core.IRoute { }
interface IRouter extends core.IRouter { } interface IRouter extends core.IRouter { }
interface IRouterHandler<T> extends core.IRouterHandler<T> { } interface IRouterHandler<T> extends core.IRouterHandler<T> { }
interface IRouterMatcher<T> extends core.IRouterMatcher<T> { } interface IRouterMatcher<T> extends core.IRouterMatcher<T> { }
interface MediaType extends core.MediaType { } interface MediaType extends core.MediaType { }
interface NextFunction extends core.NextFunction { } interface NextFunction extends core.NextFunction { }
interface Request<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, interface Request<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any,
ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery> { } ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery> { }
interface RequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, interface RequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any,
ReqQuery = core.Query> extends core.RequestHandler<P, ResBody, ReqBody, ReqQuery> { } ReqQuery = core.Query> extends core.RequestHandler<P, ResBody, ReqBody, ReqQuery> { }
interface RequestParamHandler extends core.RequestParamHandler { } interface RequestParamHandler extends core.RequestParamHandler { }
export interface Response<ResBody = any> extends core.Response<ResBody> { } export interface Response<ResBody = any> extends core.Response<ResBody> { }
interface Router extends core.Router { } interface Router extends core.Router { }
interface Send extends core.Send { } interface Send extends core.Send { }
} }
export = e; export = e;

View File

@ -9,111 +9,111 @@ import globals from '../globals';
// req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'} // req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'}
module.exports = async (req, res, next) => { module.exports = async (req, res, next) => {
let givenMethod = ''; // authorization method given by client, basic taken preferred let givenMethod = ''; // authorization method given by client, basic taken preferred
let user = {name: '', level: '', id: '', location: '', models: []}; // user object let user = {name: '', level: '', id: '', location: '', models: []}; // user object
// test authentications // test authentications
const userBasic = await basic(req, next); const userBasic = await basic(req, next);
if (userBasic) { // basic available if (userBasic) { // basic available
givenMethod = 'basic'; givenMethod = 'basic';
user = userBasic; user = userBasic;
} }
else { // if basic not available, test key else { // if basic not available, test key
const userKey = await key(req, next); const userKey = await key(req, next);
if (userKey) { if (userKey) {
givenMethod = 'key'; givenMethod = 'key';
user = userKey; user = userKey;
} }
} }
req.auth = (res, levels, method = 'all') => { req.auth = (res, levels, method = 'all') => {
if (givenMethod === method || (method === 'all' && givenMethod !== '')) { // method is available if (givenMethod === method || (method === 'all' && givenMethod !== '')) { // method is available
if (levels.indexOf(user.level) > -1) { // level is available if (levels.indexOf(user.level) > -1) { // level is available
return true; return true;
} }
else { else {
res.status(403).json({status: 'Forbidden'}); res.status(403).json({status: 'Forbidden'});
return false; return false;
} }
} }
else { else {
res.status(401).json({status: 'Unauthorized'}); res.status(401).json({status: 'Unauthorized'});
return false; return false;
} }
} }
req.authDetails = { req.authDetails = {
method: givenMethod, method: givenMethod,
username: user.name, username: user.name,
level: user.level, level: user.level,
id: user.id, id: user.id,
location: user.location, location: user.location,
models: user.models models: user.models
}; };
next(); next();
} }
function basic (req, next): any { // checks basic auth and returns changed user object function basic (req, next): any { // checks basic auth and returns changed user object
return new Promise(resolve => { return new Promise(resolve => {
const auth = basicAuth(req); const auth = basicAuth(req);
if (auth !== undefined) { // basic auth available if (auth !== undefined) { // basic auth available
UserModel.find({name: auth.name, status: globals.status.new}).lean().exec( (err, data: any) => { // find user UserModel.find({name: auth.name, status: globals.status.new}).lean().exec( (err, data: any) => { // find user
if (err) return next(err); if (err) return next(err);
if (data.length === 1) { // one user found if (data.length === 1) { // one user found
bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // check password bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // check password
if (err) return next(err); if (err) return next(err);
if (res === true) { // password correct if (res === true) { // password correct
resolve({ resolve({
level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0], level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
name: data[0].name, name: data[0].name,
id: data[0]._id.toString(), id: data[0]._id.toString(),
location: data[0].location, location: data[0].location,
models: data[0].models models: data[0].models
}); });
} }
else { else {
resolve(null); resolve(null);
} }
}); });
} }
else { else {
resolve(null); resolve(null);
} }
}); });
} }
else { else {
resolve(null); resolve(null);
} }
}); });
} }
function key (req, next): any { // checks API key and returns changed user object function key (req, next): any { // checks API key and returns changed user object
return new Promise(resolve => { return new Promise(resolve => {
if (req.query.key !== undefined) { // key available if (req.query.key !== undefined) { // key available
UserModel.find({key: req.query.key, status: globals.status.new}).lean().exec( (err, data: any) => { // find user UserModel.find({key: req.query.key, status: globals.status.new}).lean().exec( (err, data: any) => { // find user
if (err) return next(err); if (err) return next(err);
if (data.length === 1) { // one user found if (data.length === 1) { // one user found
resolve({ resolve({
level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0], level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
name: data[0].name, name: data[0].name,
id: data[0]._id.toString(), id: data[0]._id.toString(),
location: data[0].location, location: data[0].location,
models: data[0].models models: data[0].models
}); });
if (!/^\/api/m.test(req.url)){ if (!/^\/api/m.test(req.url)){
delete req.query.key; // delete query parameter to avoid interference with later validation delete req.query.key; // delete query parameter to avoid interference with later validation
} }
} }
else { else {
resolve(null); resolve(null);
} }
}); });
} }
else { else {
resolve(null); resolve(null);
} }
}); });
} }

View File

@ -2,8 +2,8 @@ import {parseAsync} from 'json2csv';
import flatten from './flatten'; import flatten from './flatten';
export default function csv(input: any[], f: (err, data) => void) { // parse JSON to CSV export default function csv(input: any[], f: (err, data) => void) { // parse JSON to CSV
parseAsync(input.map(e => flatten(e)), {includeEmptyRows: true}) parseAsync(input.map(e => flatten(e)), {includeEmptyRows: true})
.then(csv => f(null, csv)) .then(csv => f(null, csv))
.catch(err => f(err, null)); .catch(err => f(err, null));
} }

View File

@ -1,42 +1,42 @@
import globals from '../globals'; import globals from '../globals';
export default function flatten (data, keepArray = false) { // flatten object: {a: {b: true}} -> {a.b: true} export default function flatten (data, keepArray = false) { // flatten object: {a: {b: true}} -> {a.b: true}
const result = {}; const result = {};
function recurse (cur, prop) { function recurse (cur, prop) {
if (Object(cur) !== cur || Object.keys(cur).length === 0) { // simple value if (Object(cur) !== cur || Object.keys(cur).length === 0) { // simple value
result[prop] = cur; result[prop] = cur;
} }
else if (prop === `${globals.spectrum.spectrum}.${globals.spectrum.dpt}`) { // convert spectrum for ML else if (prop === `${globals.spectrum.spectrum}.${globals.spectrum.dpt}`) { // convert spectrum for ML
result[prop + '.labels'] = cur.map(e => parseFloat(e[0])); result[prop + '.labels'] = cur.map(e => parseFloat(e[0]));
result[prop + '.values'] = cur.map(e => parseFloat(e[1])); result[prop + '.values'] = cur.map(e => parseFloat(e[1]));
} }
else if (Array.isArray(cur)) { else if (Array.isArray(cur)) {
if (keepArray) { if (keepArray) {
result[prop] = cur; result[prop] = cur;
} }
else { // array to string else { // array to string
if (cur.length && (Object(cur[0]) !== cur || Object.keys(cur[0]).length === 0)) { // array of non-objects if (cur.length && (Object(cur[0]) !== cur || Object.keys(cur[0]).length === 0)) { // array of non-objects
result[prop] = '[' + cur.join(', ') + ']'; result[prop] = '[' + cur.join(', ') + ']';
} }
else { else {
let l = 0; let l = 0;
for(let i = 0, l = cur.length; i < l; i++) for(let i = 0, l = cur.length; i < l; i++)
recurse(cur[i], prop + "[" + i + "]"); recurse(cur[i], prop + "[" + i + "]");
if (l == 0) if (l == 0)
result[prop] = []; result[prop] = [];
} }
} }
} }
else { // object else { // object
let isEmpty = true; let isEmpty = true;
for (let p in cur) { for (let p in cur) {
isEmpty = false; isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p); recurse(cur[p], prop ? prop+"."+p : p);
} }
if (isEmpty && prop) if (isEmpty && prop)
result[prop] = {}; result[prop] = {};
} }
} }
recurse(data, ''); recurse(data, '');
return result; return result;
} }

View File

@ -4,86 +4,86 @@ import axios from 'axios';
export default class Mail{ export default class Mail{
static readonly address = 'definma@bosch-iot.com'; // email address static readonly address = 'definma@bosch-iot.com'; // email address
static uri: string; // mail API URI static uri: string; // mail API URI
static auth = {username: '', password: ''}; // mail API credentials static auth = {username: '', password: ''}; // mail API credentials
static mailPass: string; // mail API generates password static mailPass: string; // mail API generates password
static init() { static init() {
if (process.env.NODE_ENV === 'production') { // only send mails in production 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.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.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.username = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.username;
this.auth.password = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.password; this.auth.password = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.password;
axios({ // get registered mail addresses axios({ // get registered mail addresses
method: 'get', method: 'get',
url: this.uri + '/management/userDomainMapping', url: this.uri + '/management/userDomainMapping',
auth: this.auth auth: this.auth
}).then(res => { }).then(res => {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
if (res.data.addresses.indexOf(this.address) < 0) { // mail address not registered if (res.data.addresses.indexOf(this.address) < 0) { // mail address not registered
if (res.data.addresses.length) { // delete wrong registered mail address if (res.data.addresses.length) { // delete wrong registered mail address
await axios({ await axios({
method: 'delete', method: 'delete',
url: this.uri + '/management/mailAddresses/' + res.data.addresses[0], url: this.uri + '/management/mailAddresses/' + res.data.addresses[0],
auth: this.auth auth: this.auth
}); });
} }
await axios({ // register right mail address await axios({ // register right mail address
method: 'post', method: 'post',
url: this.uri + '/management/mailAddresses/' + this.address, url: this.uri + '/management/mailAddresses/' + this.address,
auth: this.auth auth: this.auth
}); });
} }
resolve(); resolve();
} }
catch (e) { catch (e) {
reject(e); reject(e);
} }
}); });
}).then(() => { }).then(() => {
return axios({ // set new mail password return axios({ // set new mail password
method: 'put', method: 'put',
url: this.uri + '/management/mailAddresses/' + this.address + '/password/' + this.mailPass, url: this.uri + '/management/mailAddresses/' + this.address + '/password/' + this.mailPass,
auth: this.auth auth: this.auth
}); });
}).then(() => { // init done successfully }).then(() => { // init done successfully
console.info('Mail service established successfully'); console.info('Mail service established successfully');
}).catch(err => { // somewhere an error occurred }).catch(err => { // somewhere an error occurred
console.error(`Mail init error: ${err.request.method} ${err.request.path}: ${err.response.status}`, console.error(`Mail init error: ${err.request.method} ${err.request.path}: ${err.response.status}`,
err.response.data); err.response.data);
}); });
} }
} }
static send (mailAddress, subject, content, f: (x?) => void = () => {}) { // callback executed empty or with error 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 if (process.env.NODE_ENV === 'production') { // only send mails in production
axios({ axios({
method: 'post', method: 'post',
url: this.uri + '/email', url: this.uri + '/email',
auth: this.auth, auth: this.auth,
data: { data: {
recipients: [{to: mailAddress}], recipients: [{to: mailAddress}],
subject: {content: subject}, subject: {content: subject},
body: { body: {
content: content, content: content,
contentType: "text/html" contentType: "text/html"
}, },
from: { from: {
eMail: this.address, eMail: this.address,
password: this.mailPass password: this.mailPass
} }
} }
}).then(() => { }).then(() => {
f(); f();
}).catch((err) => { }).catch((err) => {
f(err); f(err);
}); });
} }
else { // dev dummy replacement else { // dev dummy replacement
console.info('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content); console.info('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
f(); f();
} }
} }
} }

View File

@ -1,11 +1,11 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
const ChangelogSchema = new mongoose.Schema({ const ChangelogSchema = new mongoose.Schema({
action: String, action: String,
collection_name: String, collection_name: String,
conditions: mongoose.Schema.Types.Mixed, conditions: mongoose.Schema.Types.Mixed,
data: Object, data: Object,
user_id: mongoose.Schema.Types.ObjectId user_id: mongoose.Schema.Types.ObjectId
}, {minimize: false, strict: false}); }, {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);

View File

@ -2,19 +2,19 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const ConditionTemplateSchema = new mongoose.Schema({ const ConditionTemplateSchema = new mongoose.Schema({
first_id: mongoose.Schema.Types.ObjectId, first_id: mongoose.Schema.Types.ObjectId,
name: String, name: String,
version: Number, version: Number,
parameters: [new mongoose.Schema({ parameters: [new mongoose.Schema({
name: String, name: String,
range: mongoose.Schema.Types.Mixed range: mongoose.Schema.Types.Mixed
} ,{ _id : false })] } ,{ _id : false })]
}, {minimize: false}); // to allow empty objects }, {minimize: false}); // to allow empty objects
// changelog query helper // changelog query helper
ConditionTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { ConditionTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -2,15 +2,15 @@ import db from '../db';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
const HelpSchema = new mongoose.Schema({ const HelpSchema = new mongoose.Schema({
key: {type: String, index: {unique: true}}, key: {type: String, index: {unique: true}},
level: String, level: String,
text: String text: String
}, {minimize: false}); }, {minimize: false});
// changelog query helper // changelog query helper
HelpSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { HelpSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -4,20 +4,20 @@ import MaterialGroupsModel from '../models/material_groups';
import db from '../db'; import db from '../db';
const MaterialSchema = new mongoose.Schema({ const MaterialSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}}, name: {type: String, index: {unique: true}},
supplier_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialSupplierModel}, supplier_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialSupplierModel},
group_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialGroupsModel}, group_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialGroupsModel},
properties: mongoose.Schema.Types.Mixed, properties: mongoose.Schema.Types.Mixed,
numbers: [String], numbers: [String],
status: String status: String
}, {minimize: false}); }, {minimize: false});
// changelog query helper // changelog query helper
MaterialSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { MaterialSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return this; return this;
} }
MaterialSchema.index({supplier_id: 1}); MaterialSchema.index({supplier_id: 1});
MaterialSchema.index({group_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);

View File

@ -2,13 +2,13 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const MaterialGroupsSchema = new mongoose.Schema({ const MaterialGroupsSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}} name: {type: String, index: {unique: true}}
}); });
// changelog query helper // changelog query helper
MaterialGroupsSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { MaterialGroupsSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -2,13 +2,13 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const MaterialSuppliersSchema = new mongoose.Schema({ const MaterialSuppliersSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}} name: {type: String, index: {unique: true}}
}); });
// changelog query helper // changelog query helper
MaterialSuppliersSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { MaterialSuppliersSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -2,19 +2,19 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const MaterialTemplateSchema = new mongoose.Schema({ const MaterialTemplateSchema = new mongoose.Schema({
first_id: mongoose.Schema.Types.ObjectId, first_id: mongoose.Schema.Types.ObjectId,
name: String, name: String,
version: Number, version: Number,
parameters: [new mongoose.Schema({ parameters: [new mongoose.Schema({
name: String, name: String,
range: mongoose.Schema.Types.Mixed range: mongoose.Schema.Types.Mixed
} ,{ _id : false })] } ,{ _id : false })]
}, {minimize: false}); // to allow empty objects }, {minimize: false}); // to allow empty objects
// changelog query helper // changelog query helper
MaterialTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { MaterialTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -6,18 +6,18 @@ import db from '../db';
const MeasurementSchema = new mongoose.Schema({ const MeasurementSchema = new mongoose.Schema({
sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel}, sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel},
values: mongoose.Schema.Types.Mixed, values: mongoose.Schema.Types.Mixed,
measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel}, measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel},
status: String status: String
}, {minimize: false}); }, {minimize: false});
// changelog query helper // changelog query helper
MeasurementSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { MeasurementSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return this; return this;
} }
MeasurementSchema.index({sample_id: 1}); MeasurementSchema.index({sample_id: 1});
MeasurementSchema.index({measurement_template: 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);

View File

@ -2,19 +2,19 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const MeasurementTemplateSchema = new mongoose.Schema({ const MeasurementTemplateSchema = new mongoose.Schema({
first_id: mongoose.Schema.Types.ObjectId, first_id: mongoose.Schema.Types.ObjectId,
name: String, name: String,
version: Number, version: Number,
parameters: [new mongoose.Schema({ parameters: [new mongoose.Schema({
name: String, name: String,
range: mongoose.Schema.Types.Mixed range: mongoose.Schema.Types.Mixed
} ,{ _id : false })] } ,{ _id : false })]
}, {minimize: false}); // to allow empty objects }, {minimize: false}); // to allow empty objects
// changelog query helper // changelog query helper
MeasurementTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { MeasurementTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -2,19 +2,19 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const ModelSchema = new mongoose.Schema({ const ModelSchema = new mongoose.Schema({
group: {type: String, index: {unique: true}}, group: {type: String, index: {unique: true}},
models: [new mongoose.Schema({ models: [new mongoose.Schema({
name: String, name: String,
url: String, url: String,
label: String label: String
} ,{ _id : true })] } ,{ _id : true })]
}); });
// changelog query helper // changelog query helper
ModelSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { ModelSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return this; return this;
} }
ModelSchema.index({group: 1}); 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);

View File

@ -1,8 +1,8 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
const ModelFileSchema = new mongoose.Schema({ const ModelFileSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}}, name: {type: String, index: {unique: true}},
data: Buffer 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);

View File

@ -2,18 +2,18 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const NoteSchema = new mongoose.Schema({ const NoteSchema = new mongoose.Schema({
comment: String, comment: String,
sample_references: [{ sample_references: [{
sample_id: mongoose.Schema.Types.ObjectId, sample_id: mongoose.Schema.Types.ObjectId,
relation: String relation: String
}], }],
custom_fields: mongoose.Schema.Types.Mixed custom_fields: mongoose.Schema.Types.Mixed
}); });
// changelog query helper // changelog query helper
NoteSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { NoteSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -2,14 +2,14 @@ import mongoose from 'mongoose';
import db from '../db'; import db from '../db';
const NoteFieldSchema = new mongoose.Schema({ const NoteFieldSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}}, name: {type: String, index: {unique: true}},
qty: Number qty: Number
}); });
// changelog query helper // changelog query helper
NoteFieldSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { NoteFieldSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -6,24 +6,24 @@ import UserModel from './user';
import db from '../db'; import db from '../db';
const SampleSchema = new mongoose.Schema({ const SampleSchema = new mongoose.Schema({
number: {type: String, index: {unique: true}}, number: {type: String, index: {unique: true}},
type: String, type: String,
color: String, color: String,
batch: String, batch: String,
condition: mongoose.Schema.Types.Mixed, condition: mongoose.Schema.Types.Mixed,
material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel}, material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel}, note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel}, user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel},
status: String status: String
}, {minimize: false}); }, {minimize: false});
// changelog query helper // changelog query helper
SampleSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { SampleSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return this; return this;
} }
SampleSchema.index({material_id: 1}); SampleSchema.index({material_id: 1});
SampleSchema.index({note_id: 1}); SampleSchema.index({note_id: 1});
SampleSchema.index({user_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);

View File

@ -3,21 +3,21 @@ import db from '../db';
import ModelModel from './model'; import ModelModel from './model';
const UserSchema = new mongoose.Schema({ const UserSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}}, name: {type: String, index: {unique: true}},
email: String, email: String,
pass: String, pass: String,
key: String, key: String,
level: String, level: String,
location: String, location: String,
devices: [String], devices: [String],
models: [{type: mongoose.Schema.Types.ObjectId, ref: ModelModel}], models: [{type: mongoose.Schema.Types.ObjectId, ref: ModelModel}],
status: String status: String
}); });
// changelog query helper // changelog query helper
UserSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) { UserSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
db.log(req, this); db.log(req, this);
return 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);

View File

@ -4,181 +4,181 @@ import HelpModel from '../models/help';
describe('/help', () => { describe('/help', () => {
let server; let server;
before(done => TestHelper.before(done)); before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done)); beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done)); afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done)); after(done => TestHelper.after(done));
describe('GET /help/{key}', () => { describe('GET /help/{key}', () => {
it('returns the required text', done => { it('returns the required text', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, httpStatus: 200,
res: {text: 'Samples help', level: 'read'} res: {text: 'Samples help', level: 'read'}
}); });
}); });
it('returns the required text without authorization if allowed', done => { it('returns the required text without authorization if allowed', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/help/%2Fdocumentation', url: '/help/%2Fdocumentation',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, httpStatus: 200,
res: {text: 'Documentation help', level: 'none'} res: {text: 'Documentation help', level: 'none'}
}); });
}); });
it('returns 404 for an invalid key', done => { it('returns 404 for an invalid key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/help/documentation/database', url: '/help/documentation/database',
httpStatus: 404 httpStatus: 404
}); });
}); });
it('returns 404 for an unknown key', done => { it('returns 404 for an unknown key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/help/xxx', url: '/help/xxx',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 404 httpStatus: 404
}); });
}); });
it('returns 403 for a text with a higher level than given', done => { it('returns 403 for a text with a higher level than given', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/help/%2Fmodels', url: '/help/%2Fmodels',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 403 httpStatus: 403
}); });
}); });
it('rejects an API key', done => { it('rejects an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {api: 'janedoe'}, auth: {api: 'janedoe'},
httpStatus: 401, httpStatus: 401,
}); });
}); });
it('rejects an unauthorized request if a level is given', done => { it('rejects an unauthorized request if a level is given', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
httpStatus: 401 httpStatus: 401
}); });
}); });
}); });
describe('POST /help/{key}', () => { describe('POST /help/{key}', () => {
it('changes the required text', done => { it('changes the required text', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {text: 'New samples help', level: 'write'} req: {text: 'New samples help', level: 'write'}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
should(res.body).be.eql({status: 'OK'}); should(res.body).be.eql({status: 'OK'});
HelpModel.find({key: '/samples'}).lean().exec((err, data) => { HelpModel.find({key: '/samples'}).lean().exec((err, data) => {
if (err) return done(err); if (err) return done(err);
should(data).have.lengthOf(1); should(data).have.lengthOf(1);
should(data[0]).have.only.keys('_id', 'key', 'text', 'level'); should(data[0]).have.only.keys('_id', 'key', 'text', 'level');
should(data[0]).property('key', '/samples'); should(data[0]).property('key', '/samples');
should(data[0]).property('text', 'New samples help'); should(data[0]).property('text', 'New samples help');
should(data[0]).property('level', 'write'); should(data[0]).property('level', 'write');
done(); done();
}); });
}); });
}); });
it('saves a new text', done => { it('saves a new text', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/help/%2Fmaterials', url: '/help/%2Fmaterials',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {text: 'Materials help', level: 'dev'} req: {text: 'Materials help', level: 'dev'}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
should(res.body).be.eql({status: 'OK'}); should(res.body).be.eql({status: 'OK'});
HelpModel.find({key: '/materials'}).lean().exec((err, data) => { HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
if (err) return done(err); if (err) return done(err);
should(data).have.lengthOf(1); should(data).have.lengthOf(1);
should(data[0]).have.only.keys('_id', 'key', 'text', 'level', '__v'); should(data[0]).have.only.keys('_id', 'key', 'text', 'level', '__v');
should(data[0]).property('key', '/materials'); should(data[0]).property('key', '/materials');
should(data[0]).property('text', 'Materials help'); should(data[0]).property('text', 'Materials help');
should(data[0]).property('level', 'dev'); should(data[0]).property('level', 'dev');
done(); done();
}); });
}); });
}); });
it('rejects an API key', done => { it('rejects an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {key: 'admin'}, auth: {key: 'admin'},
httpStatus: 401, httpStatus: 401,
req: {text: 'New samples help', level: 'write'} req: {text: 'New samples help', level: 'write'}
}); });
}); });
it('rejects a write user', done => { it('rejects a write user', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 403, httpStatus: 403,
req: {text: 'New samples help', level: 'write'} req: {text: 'New samples help', level: 'write'}
}); });
}); });
it('rejects an unauthorized request', done => { it('rejects an unauthorized request', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
httpStatus: 401, httpStatus: 401,
req: {text: 'New samples help', level: 'write'} req: {text: 'New samples help', level: 'write'}
}); });
}); });
}); });
describe('DELETE /help/{key}', () => { describe('DELETE /help/{key}', () => {
it('deletes the required entry', done => { it('deletes the required entry', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'delete', method: 'delete',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
should(res.body).be.eql({status: 'OK'}); should(res.body).be.eql({status: 'OK'});
HelpModel.find({key: '/materials'}).lean().exec((err, data) => { HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
if (err) return done(err); if (err) return done(err);
should(data).have.lengthOf(0); should(data).have.lengthOf(0);
done(); done();
}); });
}); });
}); });
it('rejects an API key', done => { it('rejects an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'delete', method: 'delete',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {key: 'admin'}, auth: {key: 'admin'},
httpStatus: 401 httpStatus: 401
}); });
}); });
it('rejects a write user', done => { it('rejects a write user', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'delete', method: 'delete',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 403 httpStatus: 403
}); });
}); });
it('rejects an unauthorized request', done => { it('rejects an unauthorized request', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'delete', method: 'delete',
url: '/help/%2Fsamples', url: '/help/%2Fsamples',
httpStatus: 401 httpStatus: 401
}); });
}); });
}); });
}); });

View File

@ -7,49 +7,49 @@ import globals from '../globals';
const router = express.Router(); const router = express.Router();
router.get('/help/:key', (req, res, next) => { router.get('/help/:key', (req, res, next) => {
const {error: paramError, value: key} = HelpValidate.params(req.params); const {error: paramError, value: key} = HelpValidate.params(req.params);
if (paramError) return res400(paramError, res); if (paramError) return res400(paramError, res);
HelpModel.findOne(key).lean().exec((err, data) => { HelpModel.findOne(key).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
if (data.level !== 'none') { // check level if (data.level !== 'none') { // check level
if (!req.auth(res, if (!req.auth(res,
Object.values(globals.levels).slice(Object.values(globals.levels).findIndex(e => e === data.level)) Object.values(globals.levels).slice(Object.values(globals.levels).findIndex(e => e === data.level))
, 'basic')) return; , 'basic')) return;
} }
res.json(HelpValidate.output(data)); res.json(HelpValidate.output(data));
}) })
}); });
router.post('/help/:key', (req, res, next) => { router.post('/help/:key', (req, res, next) => {
if (!req.auth(res, ['dev', 'admin'], 'basic')) return; if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
const {error: paramError, value: key} = HelpValidate.params(req.params); const {error: paramError, value: key} = HelpValidate.params(req.params);
if (paramError) return res400(paramError, res); if (paramError) return res400(paramError, res);
const {error, value: help} = HelpValidate.input(req.body); const {error, value: help} = HelpValidate.input(req.body);
if (error) return res400(error, res); if (error) return res400(error, res);
HelpModel.findOneAndUpdate(key, help, {upsert: true}).log(req).lean().exec(err => { HelpModel.findOneAndUpdate(key, help, {upsert: true}).log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
}); });
router.delete('/help/:key', (req, res, next) => { router.delete('/help/:key', (req, res, next) => {
if (!req.auth(res, ['dev', 'admin'], 'basic')) return; if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
const {error: paramError, value: key} = HelpValidate.params(req.params); const {error: paramError, value: key} = HelpValidate.params(req.params);
if (paramError) return res400(paramError, res); if (paramError) return res400(paramError, res);
HelpModel.findOneAndDelete(key).log(req).lean().exec((err, data) => { HelpModel.findOneAndDelete(key).log(req).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
}); });
module.exports = router; module.exports = router;

File diff suppressed because it is too large Load Diff

View File

@ -19,169 +19,169 @@ import globals from '../globals';
const router = express.Router(); const router = express.Router();
router.get('/materials', (req, res, next) => { 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} = const {error, value: filters} =
MaterialValidate.query(req.query, ['dev', 'admin'].indexOf(req.authDetails.level) >= 0); MaterialValidate.query(req.query, ['dev', 'admin'].indexOf(req.authDetails.level) >= 0);
if (error) return res400(error, res); if (error) return res400(error, res);
MaterialModel.find(filters).sort({name: 1}).populate('group_id').populate('supplier_id') MaterialModel.find(filters).sort({name: 1}).populate('group_id').populate('supplier_id')
.lean().exec((err, data) => { .lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => MaterialValidate.output(e, true)))); res.json(_.compact(data.map(e => MaterialValidate.output(e, true))));
}); });
}); });
router.get(`/materials/:state(${globals.status.new}|${globals.status.del})`, (req, res, next) => { 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') MaterialModel.find({status: req.params.state}).populate('group_id').populate('supplier_id')
.lean().exec((err, data) => { .lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => MaterialValidate.output(e)))); res.json(_.compact(data.map(e => MaterialValidate.output(e))));
}); });
}); });
router.get('/material/' + IdValidate.parameter(), (req, res, next) => { 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) => { MaterialModel.findById(req.params.id).populate('group_id').populate('supplier_id').lean().exec((err, data: any) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
// deleted materials only available for dev/admin // deleted materials only available for dev/admin
if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return; if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
res.json(MaterialValidate.output(data)); res.json(MaterialValidate.output(data));
}); });
}); });
router.put('/material/' + IdValidate.parameter(), (req, res, next) => { 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'); let {error, value: material} = MaterialValidate.input(req.body, 'change');
if (error) return res400(error, res); if (error) return res400(error, res);
MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => { MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => {
if (!materialData) { if (!materialData) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
if (materialData.status === 'deleted') { if (materialData.status === 'deleted') {
return res.status(403).json({status: 'Forbidden'}); return res.status(403).json({status: 'Forbidden'});
} }
if (material.hasOwnProperty('name') && material.name !== materialData.name) { if (material.hasOwnProperty('name') && material.name !== materialData.name) {
if (!await nameCheck(material, res, next)) return; if (!await nameCheck(material, res, next)) return;
} }
if (material.hasOwnProperty('group')) { if (material.hasOwnProperty('group')) {
material = await groupResolve(material, req, next); material = await groupResolve(material, req, next);
if (!material) return; if (!material) return;
} }
if (material.hasOwnProperty('supplier')) { if (material.hasOwnProperty('supplier')) {
material = await supplierResolve(material, req, next); material = await supplierResolve(material, req, next);
if (!material) return; if (!material) return;
} }
if (material.hasOwnProperty('properties')) { if (material.hasOwnProperty('properties')) {
if (!await propertiesCheck(material.properties, 'change', res, next, if (!await propertiesCheck(material.properties, 'change', res, next,
materialData.properties.material_template.toString() !== material.properties.material_template)) return; materialData.properties.material_template.toString() !== material.properties.material_template)) return;
} }
// check for changes // check for changes
if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), IdValidate.stringify(material))) { if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), IdValidate.stringify(material))) {
material.status = globals.status.new; // set status to new material.status = globals.status.new; // set status to new
} }
await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}) await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true})
.log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => { .log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
res.json(MaterialValidate.output(data)); res.json(MaterialValidate.output(data));
}); });
}); });
}); });
router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { 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 // 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}}) SampleModel.find({'material_id': new mongoose.Types.ObjectId(req.params.id), status: {$ne: globals.status.del}})
.lean().exec((err, data) => { .lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (data.length) { if (data.length) {
return res.status(400).json({status: 'Material still in use'}); return res.status(400).json({status: 'Material still in use'});
} }
MaterialModel.findByIdAndUpdate(req.params.id, {status: globals.status.del}) MaterialModel.findByIdAndUpdate(req.params.id, {status: globals.status.del})
.log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => { .log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (data) { if (data) {
res.json({status: 'OK'}); res.json({status: 'OK'});
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
}); });
router.put('/material/restore/' + IdValidate.parameter(), (req, res, next) => { 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) => { 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) => { 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'); let {error, value: material} = MaterialValidate.input(req.body, 'new');
if (error) return res400(error, res); if (error) return res400(error, res);
if (!await nameCheck(material, res, next)) return; if (!await nameCheck(material, res, next)) return;
material = await groupResolve(material, req, next); material = await groupResolve(material, req, next);
if (!material) return; if (!material) return;
material = await supplierResolve(material, req, next); material = await supplierResolve(material, req, next);
if (!material) return; if (!material) return;
if (!await propertiesCheck(material.properties, 'new', res, next)) return; if (!await propertiesCheck(material.properties, 'new', res, next)) return;
material.status = globals.status.new; // set status to new material.status = globals.status.new; // set status to new
await new MaterialModel(material).save(async (err, data) => { await new MaterialModel(material).save(async (err, data) => {
if (err) return next(err); if (err) return next(err);
db.log(req, 'materials', {_id: data._id}, data.toObject()); db.log(req, 'materials', {_id: data._id}, data.toObject());
await data.populate('group_id').populate('supplier_id').execPopulate().catch(err => next(err)); await data.populate('group_id').populate('supplier_id').execPopulate().catch(err => next(err));
if (data instanceof Error) return; if (data instanceof Error) return;
res.json(MaterialValidate.output(data.toObject())); res.json(MaterialValidate.output(data.toObject()));
}); });
}); });
router.get('/material/groups', (req, res, next) => { 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) => { MaterialGroupModel.find().lean().exec((err, data: any) => {
if (err) return next(err); if (err) return next(err);
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name)))); res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name))));
}); });
}); });
router.get('/material/suppliers', (req, res, next) => { 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) => { MaterialSupplierModel.find().lean().exec((err, data: any) => {
if (err) return next(err); if (err) return next(err);
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => MaterialValidate.outputSuppliers(e.name)))); 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 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; const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => next(err)) as any;
if (materialData instanceof Error) return false; if (materialData instanceof Error) return false;
if (materialData) { // could not find material_id if (materialData) { // could not find material_id
res.status(400).json({status: 'Material name already taken'}); res.status(400).json({status: 'Material name already taken'});
return false; return false;
} }
return true; return true;
} }
async function groupResolve (material, req, next) { async function groupResolve (material, req, next) {
const groupData = await MaterialGroupModel.findOneAndUpdate( const groupData = await MaterialGroupModel.findOneAndUpdate(
{name: material.group}, {name: material.group},
{name: material.group}, {name: material.group},
{upsert: true, new: true} {upsert: true, new: true}
).log(req).lean().exec().catch(err => next(err)) as any; ).log(req).lean().exec().catch(err => next(err)) as any;
if (groupData instanceof Error) return false; if (groupData instanceof Error) return false;
material.group_id = groupData._id; material.group_id = groupData._id;
delete material.group; delete material.group;
return material; return material;
} }
async function supplierResolve (material, req, next) { async function supplierResolve (material, req, next) {
const supplierData = await MaterialSupplierModel.findOneAndUpdate( const supplierData = await MaterialSupplierModel.findOneAndUpdate(
{name: material.supplier}, {name: material.supplier},
{name: material.supplier}, {name: material.supplier},
{upsert: true, new: true} {upsert: true, new: true}
).log(req).lean().exec().catch(err => next(err)) as any; ).log(req).lean().exec().catch(err => next(err)) as any;
if (supplierData instanceof Error) return false; if (supplierData instanceof Error) return false;
material.supplier_id = supplierData._id; material.supplier_id = supplierData._id;
delete material.supplier; delete material.supplier;
return material; return material;
} }
// validate material properties, returns false if invalid, otherwise template data // validate material properties, returns false if invalid, otherwise template data
async function propertiesCheck (properties, param, res, next, checkVersion = true) { async function propertiesCheck (properties, param, res, next, checkVersion = true) {
if (!properties.material_template || !IdValidate.valid(properties.material_template)) { // template id not found if (!properties.material_template || !IdValidate.valid(properties.material_template)) { // template id not found
res.status(400).json({status: 'Material template not available'}); res.status(400).json({status: 'Material template not available'});
return false; return false;
} }
const materialData = await MaterialTemplateModel.findById(properties.material_template) const materialData = await MaterialTemplateModel.findById(properties.material_template)
.lean().exec().catch(err => next(err)) as any; .lean().exec().catch(err => next(err)) as any;
if (materialData instanceof Error) return false; if (materialData instanceof Error) return false;
if (!materialData) { // template not found if (!materialData) { // template not found
res.status(400).json({status: 'Material template not available'}); res.status(400).json({status: 'Material template not available'});
return false; return false;
} }
if (checkVersion) { if (checkVersion) {
// get all template versions and check if given is latest // get all template versions and check if given is latest
const materialVersions = await MaterialTemplateModel.find({first_id: materialData.first_id}).sort({version: -1}) const materialVersions = await MaterialTemplateModel.find({first_id: materialData.first_id}).sort({version: -1})
.lean().exec().catch(err => next(err)) as any; .lean().exec().catch(err => next(err)) as any;
if (materialVersions instanceof Error) return false; if (materialVersions instanceof Error) return false;
if (properties.material_template !== materialVersions[0]._id.toString()) { // template not latest if (properties.material_template !== materialVersions[0]._id.toString()) { // template not latest
res.status(400).json({status: 'Old template version not allowed'}); res.status(400).json({status: 'Old template version not allowed'});
return false; return false;
} }
} }
// validate parameters // validate parameters
const {error, value} = ParametersValidate const {error, value} = ParametersValidate
.input(_.omit(properties, 'material_template'), materialData.parameters, param); .input(_.omit(properties, 'material_template'), materialData.parameters, param);
if (error) {res400(error, res); return false;} if (error) {res400(error, res); return false;}
Object.keys(value).forEach(key => { Object.keys(value).forEach(key => {
properties[key] = value[key]; properties[key] = value[key];
}); });
return materialData; return materialData;
} }
function setStatus (status, req, res, next) { // set measurement status function setStatus (status, req, res, next) { // set measurement status
MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => { MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,114 +16,114 @@ import mongoose from "mongoose";
const router = express.Router(); const router = express.Router();
router.get('/measurement/' + IdValidate.parameter(), (req, res, next) => { 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) => { MeasurementModel.findById(req.params.id).lean().exec((err, data: any) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
// deleted measurements only available for dev/admin // deleted measurements only available for dev/admin
if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return; 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) => { 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'); const {error, value: measurement} = MeasurementValidate.input(req.body, 'change');
if (error) return res400(error, res); if (error) return res400(error, res);
const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
if (data instanceof Error) return; if (data instanceof Error) return;
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
if (data.status === 'deleted') { if (data.status === 'deleted') {
return res.status(403).json({status: 'Forbidden'}); return res.status(403).json({status: 'Forbidden'});
} }
// add properties needed for sampleIdCheck // add properties needed for sampleIdCheck
measurement.measurement_template = data.measurement_template; measurement.measurement_template = data.measurement_template;
measurement.sample_id = data.sample_id; measurement.sample_id = data.sample_id;
if (!await sampleIdCheck(measurement, req, res, next)) return; if (!await sampleIdCheck(measurement, req, res, next)) return;
// check for changes // check for changes
if (measurement.values) { // fill not changed values from database if (measurement.values) { // fill not changed values from database
measurement.values = _.assign({}, data.values, measurement.values); measurement.values = _.assign({}, data.values, measurement.values);
if (!_.isEqual(measurement.values, data.values)) { if (!_.isEqual(measurement.values, data.values)) {
measurement.status = globals.status.new; // set status to new measurement.status = globals.status.new; // set status to new
} }
} }
if (!await templateCheck(measurement, 'change', res, next)) return; if (!await templateCheck(measurement, 'change', res, next)) return;
await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}) await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true})
.log(req).lean().exec((err, data) => { .log(req).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
res.json(MeasurementValidate.output(data, req)); res.json(MeasurementValidate.output(data, req));
}); });
}); });
router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => { 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) => { MeasurementModel.findById(req.params.id).lean().exec(async (err, data) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
if (!await sampleIdCheck(data, req, res, next)) return; if (!await sampleIdCheck(data, req, res, next)) return;
await MeasurementModel.findByIdAndUpdate(req.params.id, {status: globals.status.del}) await MeasurementModel.findByIdAndUpdate(req.params.id, {status: globals.status.del})
.log(req).lean().exec(err => { .log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
return res.json({status: 'OK'}); return res.json({status: 'OK'});
}); });
}); });
}); });
router.get('/measurement/sample/' + IdValidate.parameter(), (req, res, next) => { 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) => { MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data: any) => {
if (err) return next(err); if (err) return next(err);
if (!data.length) { if (!data.length) {
return res.status(404).json({status: 'Not found'}); 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) => { 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) => { 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) => { 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'); const {error, value: measurement} = MeasurementValidate.input(req.body, 'new');
if (error) return res400(error, res); if (error) return res400(error, res);
if (!await sampleIdCheck(measurement, req, res, next)) return; if (!await sampleIdCheck(measurement, req, res, next)) return;
measurement.values = await templateCheck(measurement, 'new', res, next); measurement.values = await templateCheck(measurement, 'new', res, next);
if (!measurement.values) return; if (!measurement.values) return;
measurement.status = globals.status.new; measurement.status = globals.status.new;
await new MeasurementModel(measurement).save((err, data) => { await new MeasurementModel(measurement).save((err, data) => {
if (err) return next(err); if (err) return next(err);
db.log(req, 'measurements', {_id: data._id}, data.toObject()); db.log(req, 'measurements', {_id: data._id}, data.toObject());
res.json(MeasurementValidate.output(data.toObject(), req)); 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 // validate sample_id, returns false if invalid or user has no access for this sample
async function sampleIdCheck (measurement, req, res, next) { async function sampleIdCheck (measurement, req, res, next) {
const sampleData = await SampleModel.findById(measurement.sample_id) const sampleData = await SampleModel.findById(measurement.sample_id)
.lean().exec().catch(err => {next(err); return false;}) as any; .lean().exec().catch(err => {next(err); return false;}) as any;
if (!sampleData) { // sample_id not found if (!sampleData) { // sample_id not found
res.status(400).json({status: 'Sample id not available'}); res.status(400).json({status: 'Sample id not available'});
return false return false
} }
// sample does not belong to user // sample does not belong to user
return !(sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic')); 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, // validate measurement_template and values, returns values, true if values are {} or false if invalid,
// param for 'new'/'change' // param for 'new'/'change'
async function templateCheck (measurement, param, res, next) { async function templateCheck (measurement, param, res, next) {
const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template) const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template)
.lean().exec().catch(err => {next(err); return false;}) as any; .lean().exec().catch(err => {next(err); return false;}) as any;
if (!templateData) { // template not found if (!templateData) { // template not found
res.status(400).json({status: 'Measurement template not available'}); res.status(400).json({status: 'Measurement template not available'});
return false return false
} }
// fill not given values for new measurements // fill not given values for new measurements
if (param === 'new') { if (param === 'new') {
// get all template versions and check if given is latest // get all template versions and check if given is latest
const templateVersions = await MeasurementTemplateModel.find({first_id: templateData.first_id}).sort({version: -1}) const templateVersions = await MeasurementTemplateModel.find({first_id: templateData.first_id}).sort({version: -1})
.lean().exec().catch(err => next(err)) as any; .lean().exec().catch(err => next(err)) as any;
if (templateVersions instanceof Error) return false; if (templateVersions instanceof Error) return false;
if (measurement.measurement_template !== templateVersions[0]._id.toString()) { // template not latest if (measurement.measurement_template !== templateVersions[0]._id.toString()) { // template not latest
res.status(400).json({status: 'Old template version not allowed'}); res.status(400).json({status: 'Old template version not allowed'});
return false; return false;
} }
if (Object.keys(measurement.values).length === 0) { if (Object.keys(measurement.values).length === 0) {
res.status(400).json({status: 'At least one value is required'}); res.status(400).json({status: 'At least one value is required'});
return false return false
} }
const fillValues = {}; // initialize not given values with null const fillValues = {}; // initialize not given values with null
templateData.parameters.forEach(parameter => { templateData.parameters.forEach(parameter => {
fillValues[parameter.name] = null; fillValues[parameter.name] = null;
}); });
measurement.values = _.assign({}, fillValues, measurement.values); measurement.values = _.assign({}, fillValues, measurement.values);
} }
// validate values // validate values
const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null'); const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null');
if (error) {res400(error, res); return false;} if (error) {res400(error, res); return false;}
return value || true; return value || true;
} }
function setStatus (status, req, res, next) { // set measurement status function setStatus (status, req, res, next) { // set measurement status
MeasurementModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => { MeasurementModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@ -13,162 +13,162 @@ import mongoose from "mongoose";
const router = express.Router(); const router = express.Router();
router.get('/model/groups', (req, res, next) => { 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 = [{}, {}]; let conditions: any = [{}, {}];
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // if not dev or admin, user has to possess model rights if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // if not dev or admin, user has to possess model rights
conditions = [ conditions = [
{'models._id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}}, {'models._id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}},
{group: true, 'models.$': true} {group: true, 'models.$': true}
] ]
} }
ModelModel.find(...conditions).lean().exec((err, data) => { ModelModel.find(...conditions).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => ModelValidate.output(e)))); res.json(_.compact(data.map(e => ModelValidate.output(e))));
}); });
}); });
router.post('/model/:group', (req, res, next) => { 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); const {error, value: model} = ModelValidate.input(req.body);
if (error) return res400(error, res); if (error) return res400(error, res);
ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => { ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (data) { // group exists if (data) { // group exists
if (data.models.find(e => e.name === model.name)) { // name exists, overwrite if (data.models.find(e => e.name === model.name)) { // name exists, overwrite
ModelModel.findOneAndUpdate( ModelModel.findOneAndUpdate(
{$and: [{group: req.params.group}, {'models.name': model.name}]}, {$and: [{group: req.params.group}, {'models.name': model.name}]},
{'models.$': model}, {'models.$': model},
{upsert: true}).log(req).lean().exec(err => { {upsert: true}).log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}) res.json({status: 'OK'})
}); });
} }
else { // create new else { // create new
ModelModel.findOneAndUpdate( ModelModel.findOneAndUpdate(
{group: req.params.group}, {group: req.params.group},
{$push: {models: model as never}} {$push: {models: model as never}}
).log(req).lean().exec(err => { ).log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
} }
} }
else { // create new group else { // create new group
new ModelModel({group: req.params.group, models: [model]}).save((err, data) => { new ModelModel({group: req.params.group, models: [model]}).save((err, data) => {
if (err) return next(err); if (err) return next(err);
db.log(req, 'models', {_id: data._id}, data.toObject()); db.log(req, 'models', {_id: data._id}, data.toObject());
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
} }
}); });
}); });
router.delete('/model/:group(((?!file)[^\\/]+?))/:name', (req, res, next) => { 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) => { ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (!data || !data.models.find(e => e.name === req.params.name)) { if (!data || !data.models.find(e => e.name === req.params.name)) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
// delete all references in user.models // delete all references in user.models
UserModel.updateMany({}, {$pull: {models: data.models.find(e => e.name === req.params.name)._id as never}}, UserModel.updateMany({}, {$pull: {models: data.models.find(e => e.name === req.params.name)._id as never}},
{ multi: true }).log(req).lean().exec(err => { { multi: true }).log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
if (data.models.length > 1) { // only remove model if (data.models.length > 1) { // only remove model
ModelModel.findOneAndUpdate( ModelModel.findOneAndUpdate(
{group: req.params.group}, {group: req.params.group},
{$pull: {models: data.models.find(e => e.name === req.params.name) as never}} {$pull: {models: data.models.find(e => e.name === req.params.name) as never}}
).log(req).lean().exec(err => { ).log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}) res.json({status: 'OK'})
}); });
} }
else { // remove document else { // remove document
ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => { ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}) res.json({status: 'OK'})
}); });
} }
}); });
}); });
}); });
router.get('/model/files', (req, res, next) => { 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) => { ModelFileModel.find().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
res.json(data.map(e => ModelValidate.fileOutput(e))); res.json(data.map(e => ModelValidate.fileOutput(e)));
}); });
}); });
router.get('/model/file/:name', (req, res, next) => { 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) => { ModelFileModel.findOne({name: req.params.name}).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (data) { if (data) {
res.set('Content-Type', 'application/octet-stream'); res.set('Content-Type', 'application/octet-stream');
res.send(data.data.buffer); res.send(data.data.buffer);
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
router.post('/model/file/:name', bodyParser.raw({limit: '50mb'}), (req, res, next) => { 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}) ModelFileModel.replaceOne({name: req.params.name}, {name: req.params.name, data: req.body}).setOptions({upsert: true})
.lean().exec(err => { .lean().exec(err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
}); });
router.delete('/model/file/:name', (req, res, next) => { 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) => { ModelFileModel.findOneAndDelete({name: req.params.name}).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (data) { if (data) {
res.json({status: 'OK'}); res.json({status: 'OK'});
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
router.get('/model/authorized/:url', (req, res, next) => { 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 if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // if not dev or admin, user has to possess model rights
ModelModel.findOne({models: { $elemMatch: { ModelModel.findOne({models: { $elemMatch: {
url: decodeURIComponent(req.params.url), url: decodeURIComponent(req.params.url),
'_id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))} '_id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}
}}}).lean().exec((err, data) => { }}}).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (data) { if (data) {
res.json({status: 'OK'}); res.json({status: 'OK'});
} }
else { else {
res.status(403).json({status: 'Forbidden'}); res.status(403).json({status: 'Forbidden'});
} }
}); });
} }
else { else {
res.json({status: 'OK'}); res.json({status: 'OK'});
} }
}); });
module.exports = router; module.exports = router;

View File

@ -4,254 +4,254 @@ import db from '../db';
describe('/', () => { describe('/', () => {
let server; let server;
before(done => TestHelper.before(done)); before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done)); beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done)); afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done)); after(done => TestHelper.after(done));
describe('GET /', () => { describe('GET /', () => {
it('returns the root message', done => { it('returns the root message', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/', url: '/',
httpStatus: 200, httpStatus: 200,
res: {status: 'API server up and running!'} res: {status: 'API server up and running!'}
}); });
}); });
}); });
describe('GET /changelog/{timestamp}/{page}/{pagesize}', () => { describe('GET /changelog/{timestamp}/{page}/{pagesize}', () => {
it('returns the first page', done => { it('returns the first page', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/0/2', url: '/changelog/120000030000000000000000/0/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
should(res.body).have.lengthOf(2); should(res.body).have.lengthOf(2);
should(res.body[0].date).be.eql('1979-07-28T06:04:51.000Z'); 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[1].date).be.eql('1979-07-28T06:04:50.000Z');
should(res.body).matchEach(log => { should(res.body).matchEach(log => {
should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data'); should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
should(log).have.property('_id').be.type('string'); should(log).have.property('_id').be.type('string');
should(log).have.property('action', 'PUT /sample/400000000000000000000001'); should(log).have.property('action', 'PUT /sample/400000000000000000000001');
should(log).have.property('collection', 'samples'); should(log).have.property('collection', 'samples');
should(log).have.property('conditions', {_id: '400000000000000000000001'}); should(log).have.property('conditions', {_id: '400000000000000000000001'});
should(log).have.property('data', {type: 'processed', status: 0}); should(log).have.property('data', {type: 'processed', status: 0});
}); });
done(); done();
}); });
}); });
it('returns another page', done => { it('returns another page', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/1/2', url: '/changelog/120000030000000000000000/1/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
should(res.body).have.lengthOf(1); should(res.body).have.lengthOf(1);
should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z'); should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z');
should(res.body).matchEach(log => { should(res.body).matchEach(log => {
should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data'); should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
should(log).have.property('_id').be.type('string'); should(log).have.property('_id').be.type('string');
should(log).have.property('action', 'PUT /sample/400000000000000000000001'); should(log).have.property('action', 'PUT /sample/400000000000000000000001');
should(log).have.property('collection', 'samples'); should(log).have.property('collection', 'samples');
should(log).have.property('conditions', {_id: '400000000000000000000001'}); should(log).have.property('conditions', {_id: '400000000000000000000001'});
should(log).have.property('data', {type: 'processed', status: 0}); should(log).have.property('data', {type: 'processed', status: 0});
done(); done();
}); });
}); });
}); });
it('returns an empty array for a page with no results', done => { it('returns an empty array for a page with no results', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/10/2', url: '/changelog/120000030000000000000000/10/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
should(res.body).have.lengthOf(0); should(res.body).have.lengthOf(0);
done(); done();
}); });
}); });
it('rejects invalid ids', done => { it('rejects invalid ids', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/12000003000000h000000000/10/2', url: '/changelog/12000003000000h000000000/10/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
res: {status: 'Invalid body format', details: 'Invalid object id'} res: {status: 'Invalid body format', details: 'Invalid object id'}
}); });
}); });
it('rejects negative page numbers', done => { it('rejects negative page numbers', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/-10/2', url: '/changelog/120000030000000000000000/-10/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
res: {status: 'Invalid body format', details: '"page" must be greater than or equal to 0'} res: {status: 'Invalid body format', details: '"page" must be greater than or equal to 0'}
}); });
}); });
it('rejects negative pagesizes', done => { it('rejects negative pagesizes', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/10/-2', url: '/changelog/120000030000000000000000/10/-2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
res: {status: 'Invalid body format', details: '"pagesize" must be greater than or equal to 0'} res: {status: 'Invalid body format', details: '"pagesize" must be greater than or equal to 0'}
}); });
}); });
it('rejects request from a write user', done => { it('rejects request from a write user', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/10/2', url: '/changelog/120000030000000000000000/10/2',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 403 httpStatus: 403
}); });
}); });
it('rejects requests from an API key', done => { it('rejects requests from an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/10/2', url: '/changelog/120000030000000000000000/10/2',
auth: {key: 'admin'}, auth: {key: 'admin'},
httpStatus: 401 httpStatus: 401
}); });
}); });
it('rejects unauthorized requests', done => { it('rejects unauthorized requests', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/120000030000000000000000/10/2', url: '/changelog/120000030000000000000000/10/2',
httpStatus: 401 httpStatus: 401
}); });
}); });
}); });
describe('Unknown routes', () => { describe('Unknown routes', () => {
it('return a 404 message', done => { it('return a 404 message', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/unknownroute', url: '/unknownroute',
httpStatus: 404 httpStatus: 404
}); });
}); });
}); });
describe('An unauthorized request', () => { describe('An unauthorized request', () => {
it('returns a 401 message', done => { it('returns a 401 message', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/authorized', url: '/authorized',
httpStatus: 401 httpStatus: 401
}); });
}); });
it('does not work with correct username', done => { it('does not work with correct username', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/authorized', url: '/authorized',
auth: {basic: {name: 'admin', pass: 'Abc123!!'}}, auth: {basic: {name: 'admin', pass: 'Abc123!!'}},
httpStatus: 401 httpStatus: 401
}); });
}); });
it('does not work with incorrect username', done => { it('does not work with incorrect username', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/authorized', url: '/authorized',
auth: {basic: {name: 'adminxx', pass: 'Abc123!!'}}, auth: {basic: {name: 'adminxx', pass: 'Abc123!!'}},
httpStatus: 401 httpStatus: 401
}); });
}); });
it('does not work with a deleted user', done => { it('does not work with a deleted user', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/authorized', url: '/authorized',
auth: {basic: {name: 'customerold', pass: 'Xyz890*)'}}, auth: {basic: {name: 'customerold', pass: 'Xyz890*)'}},
httpStatus: 401 httpStatus: 401
}); });
}); });
}); });
describe('An authorized request', () => { describe('An authorized request', () => {
it('works with an API key', done => { it('works with an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/authorized', url: '/authorized',
auth: {key: 'admin'}, auth: {key: 'admin'},
httpStatus: 200, httpStatus: 200,
res: {status: 'Authorization successful', method: 'key', level: 'admin', user_id: '000000000000000000000003'} res: {status: 'Authorization successful', method: 'key', level: 'admin', user_id: '000000000000000000000003'}
}); });
}); });
it('works with basic auth', done => { it('works with basic auth', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/authorized', url: '/authorized',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'} res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
}); });
}); });
}); });
describe('An invalid JSON body', () => { describe('An invalid JSON body', () => {
it('is rejected', done => { it('is rejected', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/', url: '/',
httpStatus: 400, httpStatus: 400,
reqType: 'json', reqType: 'json',
req: '{"xxx"}', req: '{"xxx"}',
res: {status: 'Invalid JSON body'} res: {status: 'Invalid JSON body'}
}); });
}); });
}); });
// describe('A not connected database', () => { // RUN AS LAST OR RECONNECT DATABASE!! // describe('A not connected database', () => { // RUN AS LAST OR RECONNECT DATABASE!!
// it('resolves to an 500 error', done => { // it('resolves to an 500 error', done => {
// db.disconnect(() => { // db.disconnect(() => {
// TestHelper.request(server, done, { // TestHelper.request(server, done, {
// method: 'get', // method: 'get',
// url: '/', // url: '/',
// httpStatus: 500 // httpStatus: 500
// }); // });
// }); // });
// }); // });
// }); // });
}); });
describe('The /api/{url} redirect', () => { describe('The /api/{url} redirect', () => {
let server; let server;
let counter = 0; // count number of current test method let counter = 0; // count number of current test method
before(done => { before(done => {
process.env.port = '2999'; process.env.port = '2999';
db.connect('test', done); db.connect('test', done);
}); });
beforeEach(done => { beforeEach(done => {
process.env.NODE_ENV = counter === 1 ? 'production' : 'test'; process.env.NODE_ENV = counter === 1 ? 'production' : 'test';
counter ++; counter ++;
server = TestHelper.beforeEach(server, done); server = TestHelper.beforeEach(server, done);
}); });
afterEach(done => TestHelper.afterEach(server, done)); afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done)); after(done => TestHelper.after(done));
it('returns the right method', done => { it('returns the right method', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/api/authorized', url: '/api/authorized',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'} res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
}); });
}); });
// it('is disabled in production', done => { // it('is disabled in production', done => {
// TestHelper.request(server, done, { // TestHelper.request(server, done, {
// method: 'get', // method: 'get',
// url: '/api/authorized', // url: '/api/authorized',
// auth: {basic: 'admin'}, // auth: {basic: 'admin'},
// httpStatus: 404 // httpStatus: 404
// }); // });
// }); // });
}); });

View File

@ -9,37 +9,37 @@ import _ from 'lodash';
const router = express.Router(); const router = express.Router();
router.get('/', (req, res) => { 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) => { router.get('/authorized', (req, res) => {
if (!req.auth(res, Object.values(globals.levels))) return; if (!req.auth(res, Object.values(globals.levels))) return;
res.json({ res.json({
status: 'Authorization successful', status: 'Authorization successful',
method: req.authDetails.method, method: req.authDetails.method,
level: req.authDetails.level, level: req.authDetails.level,
user_id: req.authDetails.id user_id: req.authDetails.id
}); });
}); });
router.get('/changelog/:id/:page?/:pagesize?', (req, res, next) => { 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({ const {error, value: options} = RootValidate.changelogParams({
id: req.params.id, id: req.params.id,
page: req.params.page, page: req.params.page,
pagesize: req.params.pagesize pagesize: req.params.pagesize
}); });
if (error) return res400(error, res); if (error) return res400(error, res);
ChangelogModel.find({_id: {$lte: mongoose.Types.ObjectId(options.id)}}) ChangelogModel.find({_id: {$lte: mongoose.Types.ObjectId(options.id)}})
.sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize) .sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize)
.lean().exec((err, data) => { .lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => RootValidate.changelogOutput(e)))); res.json(_.compact(data.map(e => RootValidate.changelogOutput(e))));
}); });
}); });
module.exports = router; module.exports = router;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -18,141 +18,141 @@ import db from '../db';
const router = express.Router(); const router = express.Router();
router.get('/template/:collection(measurements|conditions|materials)', (req, res, next) => { 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 req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s
model(req).find({}).lean().exec((err, data) => { model(req).find({}).lean().exec((err, data) => {
if (err) next (err); if (err) next (err);
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => TemplateValidate.output(e)))); res.json(_.compact(data.map(e => TemplateValidate.output(e))));
}); });
}); });
router.get('/template/:collection(measurement|condition|material)/' + IdValidate.parameter(), (req, res, next) => { 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) => { model(req).findById(req.params.id).lean().exec((err, data) => {
if (err) next (err); if (err) next (err);
if (data) { if (data) {
res.json(TemplateValidate.output(data)); res.json(TemplateValidate.output(data));
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
router.put('/template/:collection(measurement|condition|material)/' + IdValidate.parameter(), router.put('/template/:collection(measurement|condition|material)/' + IdValidate.parameter(),
async (req, res, next) => { async (req, res, next) => {
if (!req.auth(res, ['dev', 'admin'], 'basic')) return; if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
const {error, value: template} = TemplateValidate.input(req.body, 'change'); const {error, value: template} = TemplateValidate.input(req.body, 'change');
if (error) return res400(error, res); if (error) return res400(error, res);
// find given template // find given template
const templateRef = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; const templateRef = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
if (templateRef instanceof Error) return; if (templateRef instanceof Error) return;
if (!templateRef) { if (!templateRef) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
// find latest version // find latest version
const templateData = await model(req).findOne({first_id: templateRef.first_id}).sort({version: -1}) const templateData = await model(req).findOne({first_id: templateRef.first_id}).sort({version: -1})
.lean().exec().catch(err => {next(err);}) as any; .lean().exec().catch(err => {next(err);}) as any;
if (templateData instanceof Error) return; if (templateData instanceof Error) return;
if (!templateData) { if (!templateData) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed
if (!template.parameters || _.isEqual(templateData.parameters, template.parameters)) { // only name 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}) model(req).findByIdAndUpdate(req.params.id, {name: template.name}, {new: true})
.log(req).lean().exec((err, data) => { .log(req).lean().exec((err, data) => {
if (err) next (err); if (err) next (err);
res.json(TemplateValidate.output(data)); res.json(TemplateValidate.output(data));
}); });
} }
else if (template.parameters.filter((e, i) => _.isEqual(e.range, templateData.parameters[i].range)).length else if (template.parameters.filter((e, i) => _.isEqual(e.range, templateData.parameters[i].range)).length
=== templateData.parameters.length) { // only names changed === templateData.parameters.length) { // only names changed
const changedParameterNames = template.parameters.map((e, i) => ( // list of new names const changedParameterNames = template.parameters.map((e, i) => ( // list of new names
{name: e.name, index: i, oldName: templateData.parameters[i].name} {name: e.name, index: i, oldName: templateData.parameters[i].name}
)).filter(e => e.name !== e.oldName); )).filter(e => e.name !== e.oldName);
// custom mappings for different collections // custom mappings for different collections
let targetModel; // model of the collection where the template is used let targetModel; // model of the collection where the template is used
let pathPrefix; // path to the parameters in use let pathPrefix; // path to the parameters in use
let templatePath; // complete path of the template property let templatePath; // complete path of the template property
switch (req.params.collection) { switch (req.params.collection) {
case 'condition': case 'condition':
targetModel = SampleModel; targetModel = SampleModel;
pathPrefix = 'condition.'; pathPrefix = 'condition.';
templatePath = 'condition.condition_template'; templatePath = 'condition.condition_template';
break; break;
case 'measurement': case 'measurement':
targetModel = MeasurementModel; targetModel = MeasurementModel;
pathPrefix = 'values.'; pathPrefix = 'values.';
templatePath = 'measurement_template'; templatePath = 'measurement_template';
break; break;
case 'material': case 'material':
targetModel = MaterialModel; targetModel = MaterialModel;
pathPrefix = 'properties.'; pathPrefix = 'properties.';
templatePath = 'properties.material_template'; templatePath = 'properties.material_template';
break; break;
} }
targetModel.updateMany({[templatePath]: mongoose.Types.ObjectId(templateData._id)}, targetModel.updateMany({[templatePath]: mongoose.Types.ObjectId(templateData._id)},
{$rename: {$rename:
changedParameterNames.reduce((s, e) => {s[pathPrefix + e.oldName] = pathPrefix + e.name; return s;}, {}) changedParameterNames.reduce((s, e) => {s[pathPrefix + e.oldName] = pathPrefix + e.name; return s;}, {})
}) .log(req).lean().exec(err => { }) .log(req).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
model(req).findByIdAndUpdate(req.params.id, model(req).findByIdAndUpdate(req.params.id,
{$set: {$set:
changedParameterNames.reduce( changedParameterNames.reduce(
(s, e) => {s[`parameters.${e.index}.name`] = e.name; return s;}, {name: template.name} (s, e) => {s[`parameters.${e.index}.name`] = e.name; return s;}, {name: template.name}
), ),
},{new: true}).log(req).lean().exec((err, data) => { },{new: true}).log(req).lean().exec((err, data) => {
if (err) next (err); if (err) next (err);
res.json(TemplateValidate.output(data)); res.json(TemplateValidate.output(data));
}); });
}); });
} }
else { else {
template.version = templateData.version + 1; // increase version template.version = templateData.version + 1; // increase version
// save new template, fill with old properties // save new template, fill with old properties
await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => { await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {
if (err) next (err); if (err) next (err);
db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject()); db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
res.json(TemplateValidate.output(data.toObject())); res.json(TemplateValidate.output(data.toObject()));
}); });
} }
} }
else { else {
res.json(TemplateValidate.output(templateData)); res.json(TemplateValidate.output(templateData));
} }
}); });
router.post('/template/:collection(measurement|condition|material)/new', async (req, res, next) => { router.post('/template/:collection(measurement|condition|material)/new', async (req, res, next) => {
if (!req.auth(res, ['dev', 'admin'], 'basic')) return; if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
const {error, value: template} = TemplateValidate.input(req.body, 'new'); const {error, value: template} = TemplateValidate.input(req.body, 'new');
if (error) return res400(error, res); if (error) return res400(error, res);
template._id = mongoose.Types.ObjectId(); // set reference to itself for first version of template template._id = mongoose.Types.ObjectId(); // set reference to itself for first version of template
template.first_id = template._id; template.first_id = template._id;
template.version = 1; // set template version template.version = 1; // set template version
await new (model(req))(template).save((err, data) => { await new (model(req))(template).save((err, data) => {
if (err) next (err); if (err) next (err);
db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject()); db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
res.json(TemplateValidate.output(data.toObject())); res.json(TemplateValidate.output(data.toObject()));
}); });
}); });
module.exports = router; module.exports = router;
function model (req) { // return right template model function model (req) { // return right template model
switch (req.params.collection) { switch (req.params.collection) {
case 'condition': return ConditionTemplateModel case 'condition': return ConditionTemplateModel
case 'measurement': return MeasurementTemplateModel case 'measurement': return MeasurementTemplateModel
case 'material': return MaterialTemplateModel case 'material': return MaterialTemplateModel
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -15,206 +15,206 @@ const router = express.Router();
router.get('/users', (req, res) => { 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) => { UserModel.find({}).lean().exec( (err, data:any) => {
// validate all and filter null values from validation errors // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => UserValidate.output(e, 'admin')))); 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. // 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 // See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { 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); const username = getUsername(req, res);
if (!username) return; if (!username) return;
UserModel.findOne({name: username}).lean().exec( (err, data:any) => { UserModel.findOne({name: username}).lean().exec( (err, data:any) => {
if (err) return next(err); if (err) return next(err);
if (data) { if (data) {
res.json(UserValidate.output(data)); // validate all and filter null values from validation errors res.json(UserValidate.output(data)); // validate all and filter null values from validation errors
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new // 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) => { 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); const username = getUsername(req, res);
if (!username) return; if (!username) return;
const {error, value: user} = UserValidate.input(req.body, 'change' + const {error, value: user} = UserValidate.input(req.body, 'change' +
(req.authDetails.level === 'admin'? 'admin' : '')); (req.authDetails.level === 'admin'? 'admin' : ''));
if (error) return res400(error, res); if (error) return res400(error, res);
if (user.hasOwnProperty('pass')) { if (user.hasOwnProperty('pass')) {
user.pass = bcrypt.hashSync(user.pass, 10); user.pass = bcrypt.hashSync(user.pass, 10);
} }
// check that user does not already exist if new name was specified // check that user does not already exist if new name was specified
if (user.hasOwnProperty('name') && user.name !== username) { if (user.hasOwnProperty('name') && user.name !== username) {
if (!await usernameCheck(user.name, res, next)) return; if (!await usernameCheck(user.name, res, next)) return;
} }
if (user.hasOwnProperty('models')) { if (user.hasOwnProperty('models')) {
if (!await modelsCheck(user.models, res, next)) return; if (!await modelsCheck(user.models, res, next)) return;
} }
// get current mail address to compare to given address // get current mail address to compare to given address
const oldUserData = await UserModel.findOne({name: username}).lean().exec().catch(err => next(err)); 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) => { await UserModel.findOneAndUpdate({name: username}, user, {new: true}).log(req).lean().exec( (err, data:any) => {
if (err) return next(err); if (err) return next(err);
if (data) { if (data) {
if (data.email !== oldUserData.email) { // mail address was changed, send notice to old address 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', 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 + '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 actually did this, just delete this email.' +
'<br><br>If you did not change your email, someone might be messing around with your account, ' + '<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.' + 'so talk to the sysadmin quickly!<br><br>Have a nice day.' +
'<br><br>The DeFinMa team'); '<br><br>The DeFinMa team');
} }
res.json(UserValidate.output(data)); res.json(UserValidate.output(data));
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. // 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 // See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { 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); const username = getUsername(req, res);
if (!username) return; if (!username) return;
UserModel.findOneAndUpdate({name: username}, {status: globals.status.del}).log(req).lean().exec( (err, data:any) => { UserModel.findOneAndUpdate({name: username}, {status: globals.status.del}).log(req).lean().exec( (err, data:any) => {
if (err) return next(err); if (err) return next(err);
if (data) { if (data) {
res.json({status: 'OK'}) res.json({status: 'OK'})
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
router.put('/user/restore/:username', (req, res, next) => { 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}) UserModel.findOneAndUpdate({name: req.params.username}, {status: globals.status.new})
.log(req).lean().exec((err, data) => { .log(req).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
if (!data) { if (!data) {
return res.status(404).json({status: 'Not found'}); return res.status(404).json({status: 'Not found'});
} }
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
}); });
router.get('/user/key', (req, res, next) => { 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) => { UserModel.findOne({name: req.authDetails.username}).lean().exec( (err, data:any) => {
if (err) return next(err); if (err) return next(err);
res.json({key: data.key}); res.json({key: data.key});
}); });
}); });
router.post('/user/new', async (req, res, next) => { router.post('/user/new', async (req, res, next) => {
if (!req.auth(res, ['admin'], 'basic')) return; if (!req.auth(res, ['admin'], 'basic')) return;
// validate input // validate input
const {error, value: user} = UserValidate.input(req.body, 'new'); const {error, value: user} = UserValidate.input(req.body, 'new');
if (error) return res400(error, res); if (error) return res400(error, res);
// check that user does not already exist // check that user does not already exist
if (!await usernameCheck(user.name, res, next)) return; if (!await usernameCheck(user.name, res, next)) return;
if (!await modelsCheck(user.models, res, next)) return; if (!await modelsCheck(user.models, res, next)) return;
user.key = mongoose.Types.ObjectId(); // use object id as unique API key user.key = mongoose.Types.ObjectId(); // use object id as unique API key
user.status = globals.status.new; user.status = globals.status.new;
bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing
user.pass = hash; user.pass = hash;
new UserModel(user).save((err, data) => { // store user new UserModel(user).save((err, data) => { // store user
if (err) return next(err); if (err) return next(err);
db.log(req, 'users', {_id: data._id}, data.toObject()); db.log(req, 'users', {_id: data._id}, data.toObject());
res.json(UserValidate.output(data.toObject())); res.json(UserValidate.output(data.toObject()));
}); });
}); });
}); });
router.post('/user/passreset', (req, res, next) => { router.post('/user/passreset', (req, res, next) => {
// check if user/email combo exists // check if user/email combo exists
UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => { UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
if (err) return next(err); if (err) return next(err);
if (data.length === 1) { // it exists if (data.length === 1) { // it exists
const newPass = Math.random().toString(36).substring(2); // generate temporary password const newPass = Math.random().toString(36).substring(2); // generate temporary password
bcrypt.hash(newPass, 10, (err, hash) => { // password hashing bcrypt.hash(newPass, 10, (err, hash) => { // password hashing
if (err) return next(err); if (err) return next(err);
UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}).log(req).exec(err => { // write new password UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}).log(req).exec(err => { // write new password
if (err) return next(err); if (err) return next(err);
// send email // send email
Mail.send(data[0].email, 'Your new password for the DeFinMa database', 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 + '' + '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>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 => { '<br><br>The DeFinMa team', err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
}); });
}); });
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
}); });
}); });
module.exports = router; module.exports = router;
function getUsername (req, res) { // returns username or false if action is not allowed function getUsername (req, res) { // returns username or false if action is not allowed
req.params.username = req.params[0]; // because of path regex req.params.username = req.params[0]; // because of path regex
if (req.params.username !== undefined) { // different username than request user if (req.params.username !== undefined) { // different username than request user
if (!req.auth(res, ['admin'], 'basic')) return false; if (!req.auth(res, ['admin'], 'basic')) return false;
return req.params.username; return req.params.username;
} }
else { else {
return req.authDetails.username; return req.authDetails.username;
} }
} }
async function usernameCheck (name, res, next) { // check if username is already taken 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; const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
if (userData instanceof Error) return false; if (userData instanceof Error) return false;
if (userData || UserValidate.isSpecialName(name)) { if (userData || UserValidate.isSpecialName(name)) {
res.status(400).json({status: 'Username already taken'}); res.status(400).json({status: 'Username already taken'});
return false; return false;
} }
return true; return true;
} }
async function modelsCheck (models, res, next) { // check if model ids exist, returns false on error async function modelsCheck (models, res, next) { // check if model ids exist, returns false on error
let result = true; let result = true;
for (let i in models) { for (let i in models) {
const model = await ModelModel.findOne({'models._id': mongoose.Types.ObjectId(models[i])}) const model = await ModelModel.findOne({'models._id': mongoose.Types.ObjectId(models[i])})
.lean().exec().catch(err => next(err)) as any; .lean().exec().catch(err => next(err)) as any;
if(!model) { if(!model) {
res.status(400).json({status: 'Invalid model id'}); res.status(400).json({status: 'Invalid model id'});
result = false; result = false;
break; break;
} }
} }
return result; return result;
} }

View File

@ -2,33 +2,33 @@ import Joi from 'joi';
import globals from '../../globals'; import globals from '../../globals';
export default class HelpValidate { export default class HelpValidate {
private static help = { private static help = {
text: Joi.string() text: Joi.string()
.allow('') .allow('')
.max(8192), .max(8192),
level: Joi.string() level: Joi.string()
.valid('none', ...Object.values(globals.levels)) .valid('none', ...Object.values(globals.levels))
} }
static input (data) { static input (data) {
return Joi.object({ return Joi.object({
text: this.help.text.required(), text: this.help.text.required(),
level: this.help.level.required() level: this.help.level.required()
}).validate(data); }).validate(data);
} }
static output (data) { static output (data) {
const {value, error} = Joi.object({ const {value, error} = Joi.object({
text: this.help.text, text: this.help.text,
level: this.help.level level: this.help.level
}).validate(data, {stripUnknown: true}); }).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static params(data) { static params(data) {
return Joi.object({ return Joi.object({
key: Joi.string().min(1).max(128) key: Joi.string().min(1).max(128)
}).validate(data); }).validate(data);
} }
} }

View File

@ -1,33 +1,33 @@
import Joi from 'joi'; import Joi from 'joi';
export default class IdValidate { export default class IdValidate {
private static id = Joi.string() private static id = Joi.string()
.pattern(new RegExp('[0-9a-f]{24}')) .pattern(new RegExp('[0-9a-f]{24}'))
.length(24) .length(24)
.messages({'string.pattern.base': 'Invalid object id'}); .messages({'string.pattern.base': 'Invalid object id'});
static get () { // return joi validation static get () { // return joi validation
return this.id; return this.id;
} }
static valid (id) { // validate id static valid (id) { // validate id
return this.id.validate(id).error === undefined; return this.id.validate(id).error === undefined;
} }
static parameter () { // :id url parameter static parameter () { // :id url parameter
return ':id([0-9a-f]{24})'; return ':id([0-9a-f]{24})';
} }
static stringify (data) { // convert all ObjectID objects to plain strings static stringify (data) { // convert all ObjectID objects to plain strings
Object.keys(data).forEach(key => { Object.keys(data).forEach(key => {
// stringify id // stringify id
if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') { if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
data[key] = data[key].toString(); data[key] = data[key].toString();
} }
else if (typeof data[key] === 'object' && data[key] !== null) { // deeper into recursion else if (typeof data[key] === 'object' && data[key] !== null) { // deeper into recursion
data[key] = this.stringify(data[key]); data[key] = this.stringify(data[key]);
} }
}); });
return data; return data;
} }
} }

View File

@ -4,99 +4,99 @@ import IdValidate from './id';
import globals from '../../globals'; import globals from '../../globals';
export default class MaterialValidate { // validate input for material export default class MaterialValidate { // validate input for material
private static material = { private static material = {
name: Joi.string() name: Joi.string()
.max(128), .max(128),
supplier: Joi.string() supplier: Joi.string()
.max(128), .max(128),
group: Joi.string() group: Joi.string()
.max(128), .max(128),
properties: Joi.object(), properties: Joi.object(),
numbers: Joi.array() numbers: Joi.array()
.items( .items(
Joi.string() Joi.string()
.max(64) .max(64)
), ),
status: Joi.string() status: Joi.string()
.valid(...Object.values(globals.status)) .valid(...Object.values(globals.status))
}; };
static input (data, param) { // validate input, set param to 'new' to make all attributes required static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') { if (param === 'new') {
return Joi.object({ return Joi.object({
name: this.material.name.required(), name: this.material.name.required(),
supplier: this.material.supplier.required(), supplier: this.material.supplier.required(),
group: this.material.group.required(), group: this.material.group.required(),
properties: this.material.properties.required(), properties: this.material.properties.required(),
numbers: this.material.numbers.required() numbers: this.material.numbers.required()
}).validate(data); }).validate(data);
} }
else if (param === 'change') { else if (param === 'change') {
return Joi.object({ return Joi.object({
name: this.material.name, name: this.material.name,
supplier: this.material.supplier, supplier: this.material.supplier,
group: this.material.group, group: this.material.group,
properties: this.material.properties, properties: this.material.properties,
numbers: this.material.numbers numbers: this.material.numbers
}).validate(data); }).validate(data);
} }
else { else {
return{error: 'No parameter specified!', value: {}}; return{error: 'No parameter specified!', value: {}};
} }
} }
static output (data, status = false) { // validate output and strip unwanted properties, returns null if not valid static output (data, status = false) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
data.group = data.group_id.name; data.group = data.group_id.name;
data.supplier = data.supplier_id.name; data.supplier = data.supplier_id.name;
const validate: any = { const validate: any = {
_id: IdValidate.get(), _id: IdValidate.get(),
name: this.material.name, name: this.material.name,
supplier: this.material.supplier, supplier: this.material.supplier,
group: this.material.group, group: this.material.group,
properties: this.material.properties, properties: this.material.properties,
numbers: this.material.numbers numbers: this.material.numbers
}; };
if (status) { if (status) {
validate.status = this.material.status; validate.status = this.material.status;
} }
const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true}); const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static outputGroups (data) {// validate groups output and strip unwanted properties, returns null if not valid 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}); const {value, error} = this.material.group.validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static outputSuppliers (data) {// validate suppliers output and strip unwanted properties, returns null if not valid 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}); const {value, error} = this.material.supplier.validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static outputV() { // return output validator static outputV() { // return output validator
return Joi.object({ return Joi.object({
_id: IdValidate.get(), _id: IdValidate.get(),
name: this.material.name, name: this.material.name,
supplier: this.material.supplier, supplier: this.material.supplier,
group: this.material.group, group: this.material.group,
properties: this.material.properties, properties: this.material.properties,
numbers: this.material.numbers numbers: this.material.numbers
}); });
} }
static query (data, dev = false) { static query (data, dev = false) {
const acceptedStatuses = [globals.status.val, globals.status.new]; const acceptedStatuses = [globals.status.val, globals.status.new];
if (dev) { // dev and admin can also access deleted samples if (dev) { // dev and admin can also access deleted samples
acceptedStatuses.push(globals.status.del) acceptedStatuses.push(globals.status.del)
} }
return Joi.object({ return Joi.object({
status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val]) status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val])
}).validate(data); }).validate(data);
} }
} }

View File

@ -4,64 +4,64 @@ import IdValidate from './id';
import globals from '../../globals'; import globals from '../../globals';
export default class MeasurementValidate { export default class MeasurementValidate {
private static measurement = { private static measurement = {
values: Joi.object() values: Joi.object()
.pattern(/.*/, Joi.alternatives() .pattern(/.*/, Joi.alternatives()
.try( .try(
Joi.string().max(128), Joi.string().max(128),
Joi.number(), Joi.number(),
Joi.boolean(), Joi.boolean(),
Joi.array().items(Joi.array().items(Joi.number())), // for spectra Joi.array().items(Joi.array().items(Joi.number())), // for spectra
Joi.array() Joi.array()
) )
.allow(null) .allow(null)
) )
}; };
static input (data, param) { // validate input, set param to 'new' to make all attributes required static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') { if (param === 'new') {
return Joi.object({ return Joi.object({
sample_id: IdValidate.get().required(), sample_id: IdValidate.get().required(),
values: this.measurement.values.required(), values: this.measurement.values.required(),
measurement_template: IdValidate.get().required() measurement_template: IdValidate.get().required()
}).validate(data); }).validate(data);
} }
else if (param === 'change') { else if (param === 'change') {
return Joi.object({ return Joi.object({
values: this.measurement.values values: this.measurement.values
}).validate(data); }).validate(data);
} }
else { else {
return{error: 'No parameter specified!', value: {}}; return{error: 'No parameter specified!', value: {}};
} }
} }
// validate output and strip unwanted properties, returns null if not valid // validate output and strip unwanted properties, returns null if not valid
static output (data, req, status = false) { static output (data, req, status = false) {
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
// spectral data not allowed for read/write users // spectral data not allowed for read/write users
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0 && data.values[globals.spectrum.dpt]) { if (['dev', 'admin'].indexOf(req.authDetails.level) < 0 && data.values[globals.spectrum.dpt]) {
delete data.values[globals.spectrum.dpt]; delete data.values[globals.spectrum.dpt];
} }
const validation: any = { const validation: any = {
_id: IdValidate.get(), _id: IdValidate.get(),
sample_id: IdValidate.get(), sample_id: IdValidate.get(),
values: this.measurement.values, values: this.measurement.values,
measurement_template: IdValidate.get() measurement_template: IdValidate.get()
}; };
if (status) { if (status) {
validation.status = Joi.string().valid(...Object.values(globals.status)); validation.status = Joi.string().valid(...Object.values(globals.status));
} }
const {value, error} = Joi.object(validation).validate(data, {stripUnknown: true}); const {value, error} = Joi.object(validation).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static outputV() { // return output validator static outputV() { // return output validator
return Joi.object({ return Joi.object({
_id: IdValidate.get(), _id: IdValidate.get(),
sample_id: IdValidate.get(), sample_id: IdValidate.get(),
values: this.measurement.values, values: this.measurement.values,
measurement_template: IdValidate.get() measurement_template: IdValidate.get()
}); });
} }
} }

View File

@ -3,40 +3,40 @@ import IdValidate from './id';
export default class ModelValidate { // validate input for model export default class ModelValidate { // validate input for model
private static model = { private static model = {
group: Joi.string() group: Joi.string()
.disallow('file') .disallow('file')
.max(128), .max(128),
model: Joi.object({ model: Joi.object({
name: Joi.string() name: Joi.string()
.max(128) .max(128)
.required(), .required(),
url: Joi.string() url: Joi.string()
.uri() .uri()
.max(512) .max(512)
.required() .required()
}) })
}; };
static input (data) { // validate input static input (data) { // validate input
return this.model.model.required().validate(data); return this.model.model.required().validate(data);
} }
static output (data) { // validate output and strip unwanted properties, returns null if not valid static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
const {value, error} = Joi.object({ const {value, error} = Joi.object({
group: this.model.group, group: this.model.group,
models: Joi.array().items(this.model.model.append({_id: IdValidate.get()})) models: Joi.array().items(this.model.model.append({_id: IdValidate.get()}))
}).validate(data, {stripUnknown: true}); }).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static fileOutput (data) { static fileOutput (data) {
return { return {
name: data.name, name: data.name,
size: data.data.length size: data.data.length
} }
} }
} }

View File

@ -1,18 +1,18 @@
import Joi from 'joi'; import Joi from 'joi';
export default class NoteFieldValidate { export default class NoteFieldValidate {
private static note_field = { private static note_field = {
name: Joi.string() name: Joi.string()
.max(128), .max(128),
qty: Joi.number() qty: Joi.number()
}; };
static output (data) { // validate output and strip unwanted properties, returns null if not valid static output (data) { // validate output and strip unwanted properties, returns null if not valid
const {value, error} = Joi.object({ const {value, error} = Joi.object({
name: this.note_field.name, name: this.note_field.name,
qty: this.note_field.qty qty: this.note_field.qty
}).validate(data, {stripUnknown: true}); }).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
} }

View File

@ -1,42 +1,42 @@
import Joi from 'joi'; import Joi from 'joi';
export default class ParametersValidate { export default class ParametersValidate {
// data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed) // data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed)
static input (data, parameters, param) { static input (data, parameters, param) {
let joiObject = {}; let joiObject = {};
parameters.forEach(parameter => { parameters.forEach(parameter => {
switch (parameter.range.type) { switch (parameter.range.type) {
case 'number': joiObject[parameter.name] = Joi.number(); case 'number': joiObject[parameter.name] = Joi.number();
break; break;
case 'boolean': joiObject[parameter.name] = Joi.boolean(); case 'boolean': joiObject[parameter.name] = Joi.boolean();
break; break;
case 'array': joiObject[parameter.name] = Joi.array(); case 'array': joiObject[parameter.name] = Joi.array();
break; break;
case 'string': joiObject[parameter.name] = Joi.string().max(128); case 'string': joiObject[parameter.name] = Joi.string().max(128);
break; // min or max implicitly define the value to be a number break; // min or max implicitly define the value to be a number
default: if (parameter.range.hasOwnProperty('min') || parameter.range.hasOwnProperty('max')) { default: if (parameter.range.hasOwnProperty('min') || parameter.range.hasOwnProperty('max')) {
joiObject[parameter.name] = Joi.number(); joiObject[parameter.name] = Joi.number();
} }
else { else {
joiObject[parameter.name] = Joi.string().max(128); joiObject[parameter.name] = Joi.string().max(128);
} }
} }
if (parameter.range.hasOwnProperty('min')) { if (parameter.range.hasOwnProperty('min')) {
joiObject[parameter.name] = joiObject[parameter.name].min(parameter.range.min) joiObject[parameter.name] = joiObject[parameter.name].min(parameter.range.min)
} }
if (parameter.range.hasOwnProperty('max')) { if (parameter.range.hasOwnProperty('max')) {
joiObject[parameter.name] = joiObject[parameter.name].max(parameter.range.max) joiObject[parameter.name] = joiObject[parameter.name].max(parameter.range.max)
} }
if (parameter.range.hasOwnProperty('values')) { if (parameter.range.hasOwnProperty('values')) {
joiObject[parameter.name] = joiObject[parameter.name].valid(...parameter.range.values); joiObject[parameter.name] = joiObject[parameter.name].valid(...parameter.range.values);
} }
if (parameter.range.hasOwnProperty('required') && parameter.range.required) { if (parameter.range.hasOwnProperty('required') && parameter.range.required) {
joiObject[parameter.name] = joiObject[parameter.name].required(); joiObject[parameter.name] = joiObject[parameter.name].required();
} }
if (param === 'null') { if (param === 'null') {
joiObject[parameter.name] = joiObject[parameter.name].allow(null) joiObject[parameter.name] = joiObject[parameter.name].allow(null)
} }
}); });
return Joi.object(joiObject).validate(data); return Joi.object(joiObject).validate(data);
} }
} }

View File

@ -1,5 +1,5 @@
// respond with 400 and include error details from the joi validation // respond with 400 and include error details from the joi validation
export default function res400 (error, res) { 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});
} }

View File

@ -2,50 +2,50 @@ import Joi from 'joi';
import IdValidate from './id'; import IdValidate from './id';
export default class RootValidate { // validate input for root methods export default class RootValidate { // validate input for root methods
private static changelog = { private static changelog = {
timestamp: Joi.date() timestamp: Joi.date()
.iso() .iso()
.min('1970-01-01T00:00:00.000Z'), .min('1970-01-01T00:00:00.000Z'),
page: Joi.number() page: Joi.number()
.integer() .integer()
.min(0) .min(0)
.default(0), .default(0),
pagesize: Joi.number() pagesize: Joi.number()
.integer() .integer()
.min(0) .min(0)
.default(25), .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) { static changelogParams (data) {
return Joi.object({ return Joi.object({
id: IdValidate.get(), id: IdValidate.get(),
page: this.changelog.page, page: this.changelog.page,
pagesize: this.changelog.pagesize pagesize: this.changelog.pagesize
}).validate(data); }).validate(data);
} }
static changelogOutput (data) { static changelogOutput (data) {
data.date = data._id.getTimestamp(); data.date = data._id.getTimestamp();
data.collection = data.collection_name; data.collection = data.collection_name;
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
const {value, error} = Joi.object({ const {value, error} = Joi.object({
_id: IdValidate.get(), _id: IdValidate.get(),
date: this.changelog.timestamp, date: this.changelog.timestamp,
action: this.changelog.action, action: this.changelog.action,
collection: this.changelog.collection, collection: this.changelog.collection,
conditions: this.changelog.conditions, conditions: this.changelog.conditions,
data: this.changelog.data, data: this.changelog.data,
}).validate(data, {stripUnknown: true}); }).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
} }

View File

@ -8,273 +8,273 @@ import globals from '../../globals';
export default class SampleValidate { export default class SampleValidate {
private static sample = { private static sample = {
number: Joi.string() number: Joi.string()
.max(128), .max(128),
color: Joi.string() color: Joi.string()
.max(128) .max(128)
.allow(''), .allow(''),
type: Joi.string() type: Joi.string()
.valid('as-delivered/raw', 'processed'), .valid('as-delivered/raw', 'processed'),
batch: Joi.string() batch: Joi.string()
.max(128) .max(128)
.allow(''), .allow(''),
condition: Joi.object(), condition: Joi.object(),
notes: Joi.object({ notes: Joi.object({
comment: Joi.string() comment: Joi.string()
.max(512) .max(512)
.allow('') .allow('')
.allow(null), .allow(null),
sample_references: Joi.array() sample_references: Joi.array()
.items(Joi.object({ .items(Joi.object({
sample_id: IdValidate.get(), sample_id: IdValidate.get(),
relation: Joi.string() relation: Joi.string()
.max(128) .max(128)
})), })),
custom_fields: Joi.object() custom_fields: Joi.object()
.pattern(/.*/, Joi.alternatives() .pattern(/.*/, Joi.alternatives()
.try( .try(
Joi.string().max(128), Joi.string().max(128),
Joi.number(), Joi.number(),
Joi.boolean(), Joi.boolean(),
Joi.date() Joi.date()
) )
) )
}), }),
added: Joi.date() added: Joi.date()
.iso() .iso()
.min('1970-01-01T00:00:00.000Z'), .min('1970-01-01T00:00:00.000Z'),
status: Joi.string() status: Joi.string()
.valid(...Object.values(globals.status)) .valid(...Object.values(globals.status))
}; };
static readonly sampleKeys = [ // keys which can be found in the sample directly static readonly sampleKeys = [ // keys which can be found in the sample directly
'_id', '_id',
'color', 'color',
'number', 'number',
'type', 'type',
'batch', 'batch',
'added', 'added',
'condition', 'condition',
'material_id', 'material_id',
'note_id', 'note_id',
'user_id' 'user_id'
]; ];
private static sortKeys = [ private static sortKeys = [
'_id', '_id',
'color', 'color',
'number', 'number',
'type', 'type',
'batch', 'batch',
'added', 'added',
'status', 'status',
'notes.comment', 'notes.comment',
'material.name', 'material.name',
'material.supplier', 'material.supplier',
'material.group', 'material.group',
'material.properties.*', 'material.properties.*',
'condition.*', 'condition.*',
`measurements.(?!${globals.spectrum.spectrum}.${globals.spectrum.dpt})*` `measurements.(?!${globals.spectrum.spectrum}.${globals.spectrum.dpt})*`
]; ];
private static fieldKeys = [ private static fieldKeys = [
...SampleValidate.sortKeys, ...SampleValidate.sortKeys,
'condition', 'condition',
'notes', 'notes',
'material_id', 'material_id',
'material', 'material',
'note_id', 'note_id',
'user_id', 'user_id',
'material._id', 'material._id',
'material.numbers', 'material.numbers',
'measurements', 'measurements',
`measurements.${globals.spectrum.spectrum}.${globals.spectrum.dpt}`, `measurements.${globals.spectrum.spectrum}.${globals.spectrum.dpt}`,
]; ];
static input (data, param) { // validate input, set param to 'new' to make all attributes required static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') { if (param === 'new') {
return Joi.object({ return Joi.object({
color: this.sample.color.required(), color: this.sample.color.required(),
type: this.sample.type.required(), type: this.sample.type.required(),
batch: this.sample.batch.required(), batch: this.sample.batch.required(),
condition: this.sample.condition.required(), condition: this.sample.condition.required(),
material_id: IdValidate.get().required(), material_id: IdValidate.get().required(),
notes: this.sample.notes.required() notes: this.sample.notes.required()
}).validate(data); }).validate(data);
} }
else if (param === 'change') { else if (param === 'change') {
return Joi.object({ return Joi.object({
color: this.sample.color, color: this.sample.color,
type: this.sample.type, type: this.sample.type,
batch: this.sample.batch, batch: this.sample.batch,
condition: this.sample.condition, condition: this.sample.condition,
material_id: IdValidate.get(), material_id: IdValidate.get(),
notes: this.sample.notes, notes: this.sample.notes,
}).validate(data); }).validate(data);
} }
else if (param === 'new-admin') { else if (param === 'new-admin') {
return Joi.object({ return Joi.object({
number: this.sample.number, number: this.sample.number,
color: this.sample.color.required(), color: this.sample.color.required(),
type: this.sample.type.required(), type: this.sample.type.required(),
batch: this.sample.batch.required(), batch: this.sample.batch.required(),
condition: this.sample.condition.required(), condition: this.sample.condition.required(),
material_id: IdValidate.get().required(), material_id: IdValidate.get().required(),
notes: this.sample.notes.required() notes: this.sample.notes.required()
}).validate(data); }).validate(data);
} }
else { else {
return{error: 'No parameter specified!', value: {}}; return{error: 'No parameter specified!', value: {}};
} }
} }
// validate output and strip unwanted properties, returns null if not valid // validate output and strip unwanted properties, returns null if not valid
static output (data, param = 'refs+added', additionalParams = []) { static output (data, param = 'refs+added', additionalParams = []) {
if (param === 'refs+added') { if (param === 'refs+added') {
param = 'refs'; param = 'refs';
data.added = data._id.getTimestamp(); data.added = data._id.getTimestamp();
} }
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
let joiObject; let joiObject;
if (param === 'refs') { if (param === 'refs') {
joiObject = { joiObject = {
_id: IdValidate.get(), _id: IdValidate.get(),
number: this.sample.number, number: this.sample.number,
color: this.sample.color, color: this.sample.color,
type: this.sample.type, type: this.sample.type,
batch: this.sample.batch, batch: this.sample.batch,
condition: this.sample.condition, condition: this.sample.condition,
material_id: IdValidate.get(), material_id: IdValidate.get(),
material: MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')}), material: MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')}),
note_id: IdValidate.get().allow(null), note_id: IdValidate.get().allow(null),
notes: this.sample.notes, notes: this.sample.notes,
user_id: IdValidate.get(), user_id: IdValidate.get(),
added: this.sample.added, added: this.sample.added,
status: this.sample.status status: this.sample.status
}; };
} }
else if(param === 'details') { else if(param === 'details') {
joiObject = { joiObject = {
_id: IdValidate.get(), _id: IdValidate.get(),
number: this.sample.number, number: this.sample.number,
color: this.sample.color, color: this.sample.color,
type: this.sample.type, type: this.sample.type,
batch: this.sample.batch, batch: this.sample.batch,
condition: this.sample.condition, condition: this.sample.condition,
material: MaterialValidate.outputV(), material: MaterialValidate.outputV(),
measurements: Joi.array().items(MeasurementValidate.outputV()), measurements: Joi.array().items(MeasurementValidate.outputV()),
notes: this.sample.notes, notes: this.sample.notes,
user: UserValidate.username(), user: UserValidate.username(),
status: this.sample.status status: this.sample.status
} }
} }
else { else {
return null; return null;
} }
additionalParams.forEach(param => { additionalParams.forEach(param => {
joiObject[param] = Joi.any(); joiObject[param] = Joi.any();
}); });
const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true}); const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static query (data, dev = false) { static query (data, dev = false) {
if (data.filters && data.filters.length) { if (data.filters && data.filters.length) {
const filterValidation = Joi.array().items(Joi.string()).validate(data.filters); const filterValidation = Joi.array().items(Joi.string()).validate(data.filters);
if (filterValidation.error) return filterValidation; if (filterValidation.error) return filterValidation;
try { try {
for (let i in data.filters) { for (let i in data.filters) {
try { try {
data.filters[i] = decodeURIComponent(data.filters[i]); data.filters[i] = decodeURIComponent(data.filters[i]);
} }
catch (ignore) {} catch (ignore) {}
data.filters[i] = JSON.parse(data.filters[i]); data.filters[i] = JSON.parse(data.filters[i]);
data.filters[i].values = data.filters[i].values.map(e => { // validate filter values data.filters[i].values = data.filters[i].values.map(e => { // validate filter values
if (e === null) { // null values are always allowed if (e === null) { // null values are always allowed
return null; return null;
} }
let validator; let validator;
let field = data.filters[i].field; let field = data.filters[i].field;
if (/material\./.test(field)) { // select right validation model if (/material\./.test(field)) { // select right validation model
validator = MaterialValidate.outputV().append({ validator = MaterialValidate.outputV().append({
number: Joi.string().max(128).allow(''), number: Joi.string().max(128).allow(''),
properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128).allow('')) properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128).allow(''))
}); });
field = field.replace('material.', '').split('.')[0]; field = field.replace('material.', '').split('.')[0];
} }
else if (/measurements\./.test(field) || /condition\./.test(field)) { else if (/measurements\./.test(field) || /condition\./.test(field)) {
validator = Joi.object({ validator = Joi.object({
value: Joi.alternatives() value: Joi.alternatives()
.try( .try(
Joi.number(), Joi.number(),
Joi.string().max(128).allow(''), Joi.string().max(128).allow(''),
Joi.boolean(), Joi.boolean(),
Joi.array() Joi.array()
) )
.allow(null) .allow(null)
}); });
field = 'value'; field = 'value';
} }
else if (field === 'measurements') { else if (field === 'measurements') {
validator = Joi.object({ validator = Joi.object({
value: Joi.object({}).allow(null).disallow({}) value: Joi.object({}).allow(null).disallow({})
}); });
field = 'value'; field = 'value';
} }
else if (field === 'notes.comment') { else if (field === 'notes.comment') {
field = 'comment'; field = 'comment';
validator = this.sample.notes validator = this.sample.notes
} }
else { else {
validator = Joi.object(this.sample); validator = Joi.object(this.sample);
} }
const {value, error} = validator.validate({[field]: e}); const {value, error} = validator.validate({[field]: e});
if (error) throw error; // reject invalid values if (error) throw error; // reject invalid values
return value[field]; return value[field];
}); });
} }
} }
catch (err) { catch (err) {
return {error: {details: [{message: 'Invalid JSON string for filter parameter'}]}, value: null} return {error: {details: [{message: 'Invalid JSON string for filter parameter'}]}, value: null}
} }
} }
const acceptedStatuses = [globals.status.val, globals.status.new]; const acceptedStatuses = [globals.status.val, globals.status.new];
if (dev) { // dev and admin can also access deleted samples if (dev) { // dev and admin can also access deleted samples
acceptedStatuses.push(globals.status.del) acceptedStatuses.push(globals.status.del)
} }
return Joi.object({ return Joi.object({
status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val]), status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val]),
'from-id': IdValidate.get(), 'from-id': IdValidate.get(),
'to-page': Joi.number().integer(), 'to-page': Joi.number().integer(),
'page-size': Joi.number().integer().min(1), 'page-size': Joi.number().integer().min(1),
sort: Joi.string().pattern( sort: Joi.string().pattern(
new RegExp('^(' + this.sortKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')-(asc|desc)$', 'm') new RegExp('^(' + this.sortKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')-(asc|desc)$', 'm')
).default('_id-asc'), ).default('_id-asc'),
output: Joi.string().valid('json', 'flatten', 'csv').default('json'), output: Joi.string().valid('json', 'flatten', 'csv').default('json'),
fields: Joi.array().items(Joi.string().pattern( fields: Joi.array().items(Joi.string().pattern(
new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm') new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
)).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added']) )).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added'])
.messages({'string.pattern.base': 'Invalid field name'}), .messages({'string.pattern.base': 'Invalid field name'}),
filters: Joi.array().items(Joi.object({ filters: Joi.array().items(Joi.object({
mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin', 'stringin'), mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin', 'stringin'),
field: Joi.string().pattern( field: Joi.string().pattern(
new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm') new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
).messages({'string.pattern.base': 'Invalid filter field name'}), ).messages({'string.pattern.base': 'Invalid filter field name'}),
values: Joi.array().items(Joi.alternatives().try( values: Joi.array().items(Joi.alternatives().try(
Joi.string().max(128).allow(''), Joi.number(), Joi.boolean(), Joi.date().iso(), Joi.object(), null Joi.string().max(128).allow(''), Joi.number(), Joi.boolean(), Joi.date().iso(), Joi.object(), null
)).min(1) )).min(1)
})).default([]) })).default([])
}).with('to-page', 'page-size').validate(data); }).with('to-page', 'page-size').validate(data);
} }
} }

View File

@ -2,70 +2,70 @@ import Joi from 'joi';
import IdValidate from './id'; import IdValidate from './id';
export default class TemplateValidate { export default class TemplateValidate {
private static template = { private static template = {
name: Joi.string() name: Joi.string()
.max(128), .max(128),
version: Joi.number() version: Joi.number()
.min(1), .min(1),
parameters: Joi.array() parameters: Joi.array()
.items( .items(
Joi.object({ Joi.object({
name: Joi.string() name: Joi.string()
.max(128) .max(128)
.invalid('condition_template', 'material_template') .invalid('condition_template', 'material_template')
.pattern(/^[^.]+$/) .pattern(/^[^.]+$/)
.required() .required()
.messages({'string.pattern.base': 'name must not contain a dot'}), .messages({'string.pattern.base': 'name must not contain a dot'}),
range: Joi.object({ range: Joi.object({
values: Joi.array() values: Joi.array()
.min(1), .min(1),
min: Joi.number(), min: Joi.number(),
max: Joi.number(), max: Joi.number(),
type: Joi.string() type: Joi.string()
.valid('string', 'number', 'boolean', 'array'), .valid('string', 'number', 'boolean', 'array'),
required: Joi.boolean() required: Joi.boolean()
}) })
.oxor('values', 'min') .oxor('values', 'min')
.oxor('values', 'max') .oxor('values', 'max')
.required() .required()
}) })
) )
}; };
static input (data, param) { // validate input, set param to 'new' to make all attributes required static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') { if (param === 'new') {
return Joi.object({ return Joi.object({
name: this.template.name.required(), name: this.template.name.required(),
parameters: this.template.parameters.required() parameters: this.template.parameters.required()
}).validate(data); }).validate(data);
} }
else if (param === 'change') { else if (param === 'change') {
return Joi.object({ return Joi.object({
name: this.template.name, name: this.template.name,
parameters: this.template.parameters parameters: this.template.parameters
}).validate(data); }).validate(data);
} }
else { else {
return{error: 'No parameter specified!', value: {}}; return{error: 'No parameter specified!', value: {}};
} }
} }
static output (data) { // validate output and strip unwanted properties, returns null if not valid static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
const {value, error} = Joi.object({ const {value, error} = Joi.object({
_id: IdValidate.get(), _id: IdValidate.get(),
name: this.template.name, name: this.template.name,
version: this.template.version, version: this.template.version,
first_id: IdValidate.get(), first_id: IdValidate.get(),
parameters: this.template.parameters parameters: this.template.parameters
}).validate(data, {stripUnknown: true}); }).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
} }

View File

@ -4,104 +4,104 @@ import globals from '../../globals';
import IdValidate from './id'; import IdValidate from './id';
export default class UserValidate { // validate input for user export default class UserValidate { // validate input for user
private static user = { private static user = {
name: Joi.string() name: Joi.string()
.lowercase() .lowercase()
.pattern(new RegExp('^[a-z0-9-_.]+$')) .pattern(new RegExp('^[a-z0-9-_.]+$'))
.max(128) .max(128)
.messages({'string.pattern.base': 'name must only contain a-z0-9_.'}), .messages({'string.pattern.base': 'name must only contain a-z0-9_.'}),
email: Joi.string() email: Joi.string()
.email({minDomainSegments: 2}) .email({minDomainSegments: 2})
.lowercase() .lowercase()
.max(128), .max(128),
pass: Joi.string() pass: Joi.string()
.min(8) .min(8)
.max(128), .max(128),
level: Joi.string() level: Joi.string()
.valid(...Object.values(globals.levels)), .valid(...Object.values(globals.levels)),
location: Joi.string() location: Joi.string()
.alphanum() .alphanum()
.max(128), .max(128),
devices: Joi.array() devices: Joi.array()
.items(Joi.string() .items(Joi.string()
.allow('') .allow('')
.max(128) .max(128)
), ),
models: Joi.array() models: Joi.array()
.items(IdValidate.get()), .items(IdValidate.get()),
status: Joi.string() status: Joi.string()
.valid(...Object.values(globals.status)) .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 static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') { if (param === 'new') {
return Joi.object({ return Joi.object({
name: this.user.name.required(), name: this.user.name.required(),
email: this.user.email.required(), email: this.user.email.required(),
pass: this.user.pass.required(), pass: this.user.pass.required(),
level: this.user.level.required(), level: this.user.level.required(),
location: this.user.location.required(), location: this.user.location.required(),
devices: this.user.devices.required(), devices: this.user.devices.required(),
models: this.user.models.required() models: this.user.models.required()
}).validate(data); }).validate(data);
} }
else if (param === 'change') { else if (param === 'change') {
return Joi.object({ return Joi.object({
name: this.user.name, name: this.user.name,
email: this.user.email, email: this.user.email,
pass: this.user.pass, pass: this.user.pass,
location: this.user.location, location: this.user.location,
devices: this.user.devices devices: this.user.devices
}).validate(data); }).validate(data);
} }
else if (param === 'changeadmin') { else if (param === 'changeadmin') {
return Joi.object({ return Joi.object({
name: this.user.name, name: this.user.name,
email: this.user.email, email: this.user.email,
pass: this.user.pass, pass: this.user.pass,
level: this.user.level, level: this.user.level,
location: this.user.location, location: this.user.location,
devices: this.user.devices, devices: this.user.devices,
models: this.user.models models: this.user.models
}).validate(data); }).validate(data);
} }
else { else {
return {error: 'No parameter specified!', value: {}}; return {error: 'No parameter specified!', value: {}};
} }
} }
static output (data, param = '') { // validate output and strip unwanted properties, returns null if not valid static output (data, param = '') { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
const validate: {[key: string]: object} = { const validate: {[key: string]: object} = {
_id: IdValidate.get(), _id: IdValidate.get(),
name: this.user.name, name: this.user.name,
email: this.user.email, email: this.user.email,
level: this.user.level, level: this.user.level,
location: this.user.location, location: this.user.location,
devices: this.user.devices, devices: this.user.devices,
models: this.user.models models: this.user.models
} }
if (param === 'admin') { if (param === 'admin') {
validate.status = this.user.status; validate.status = this.user.status;
} }
const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true}); const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true});
return error !== undefined? null : value; return error !== undefined? null : value;
} }
static isSpecialName (name) { // true if name belongs to special names static isSpecialName (name) { // true if name belongs to special names
return this.specialUsernames.indexOf(name) > -1; return this.specialUsernames.indexOf(name) > -1;
} }
static username() { static username() {
return this.user.name; return this.user.name;
} }
} }

View File

@ -7,141 +7,141 @@ import IdValidate from '../routes/validate/id';
export default class TestHelper { export default class TestHelper {
public static auth = { // test user credentials public static auth = { // test user credentials
admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'}, admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'},
janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'}, janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'},
user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'}, user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'},
johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'}, johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'},
customer: {pass: 'Xyz890*)', key: '000000000000000000001005', id: '000000000000000000000005'} customer: {pass: 'Xyz890*)', key: '000000000000000000001005', id: '000000000000000000000005'}
} }
public static res = { // default responses public static res = { // default responses
400: {status: 'Bad request'}, 400: {status: 'Bad request'},
401: {status: 'Unauthorized'}, 401: {status: 'Unauthorized'},
403: {status: 'Forbidden'}, 403: {status: 'Forbidden'},
404: {status: 'Not found'}, 404: {status: 'Not found'},
500: {status: 'Internal server error'} 500: {status: 'Internal server error'}
} }
static before (done) { static before (done) {
process.env.port = '2999'; process.env.port = '2999';
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
db.connect('test', done); db.connect('test', done);
} }
static beforeEach (server, done) { static beforeEach (server, done) {
// delete cached server code except models as these are needed in the testing files as well // 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 => { Object.keys(require.cache).filter(e => /API\\dist\\(?!(models|db|test))/.test(e)).forEach(key => {
delete require.cache[key]; // prevent loading from cache delete require.cache[key]; // prevent loading from cache
}); });
server = require('../index'); server = require('../index');
db.drop(err => { // reset database db.drop(err => { // reset database
if (err) return done(err); if (err) return done(err);
db.loadJson(require('./db.json'), done); db.loadJson(require('./db.json'), done);
}); });
return server return server
} }
// options in form: {method, url, contentType, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res, // 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)} // default (set to false if you want to dismiss default .end handling)}
static request (server, done, options) { static request (server, done, options) {
let st = supertest(server); let st = supertest(server);
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key
options.url += '?key=' + options.url += '?key=' +
(this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key); (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
} }
switch (options.method) { // http method switch (options.method) { // http method
case 'get': case 'get':
st = st.get(options.url) st = st.get(options.url)
break; break;
case 'post': case 'post':
st = st.post(options.url) st = st.post(options.url)
break; break;
case 'put': case 'put':
st = st.put(options.url) st = st.put(options.url)
break; break;
case 'delete': case 'delete':
st = st.delete(options.url) st = st.delete(options.url)
break; break;
} }
if (options.hasOwnProperty('reqType')) { // request body if (options.hasOwnProperty('reqType')) { // request body
st = st.type(options.reqType); st = st.type(options.reqType);
} }
if (options.hasOwnProperty('req')) { // request body if (options.hasOwnProperty('req')) { // request body
st = st.send(options.req); st = st.send(options.req);
} }
if (options.hasOwnProperty('reqContentType')) { // request body if (options.hasOwnProperty('reqContentType')) { // request body
st = st.set('Content-Type', options.reqContentType); st = st.set('Content-Type', options.reqContentType);
} }
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) { // resolve basic auth if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) { // resolve basic auth
if (this.auth.hasOwnProperty(options.auth.basic)) { if (this.auth.hasOwnProperty(options.auth.basic)) {
st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass) st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
} }
else { else {
st = st.auth(options.auth.basic.name, options.auth.basic.pass) st = st.auth(options.auth.basic.name, options.auth.basic.pass)
} }
} }
if (options.hasOwnProperty('contentType')) { if (options.hasOwnProperty('contentType')) {
st = st.expect('Content-type', options.contentType).expect(options.httpStatus); st = st.expect('Content-type', options.contentType).expect(options.httpStatus);
} }
else { else {
st = st.expect('Content-type', /json/).expect(options.httpStatus); st = st.expect('Content-type', /json/).expect(options.httpStatus);
} }
if (options.hasOwnProperty('res')) { // evaluate result if (options.hasOwnProperty('res')) { // evaluate result
return st.end((err, res) => { return st.end((err, res) => {
if (err) return done (err); if (err) return done (err);
should(res.body).be.eql(options.res); should(res.body).be.eql(options.res);
done(); done();
}); });
} }
else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) { // evaluate default results else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) { // evaluate default results
return st.end((err, res) => { return st.end((err, res) => {
if (err) return done (err); if (err) return done (err);
should(res.body).be.eql(this.res[options.httpStatus]); should(res.body).be.eql(this.res[options.httpStatus]);
done(); done();
}); });
} }
// check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)} // check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)}
else if (options.hasOwnProperty('log')) { else if (options.hasOwnProperty('log')) {
return st.end(err => { return st.end(err => {
if (err) return done (err); if (err) return done (err);
ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0) ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0)
.lean().exec((err, data) => { // latest entry .lean().exec((err, data) => { // latest entry
if (err) return done(err); if (err) return done(err);
should(data).have.only.keys('_id', 'action', 'collection_name', 'conditions', 'data', 'user_id', '__v'); 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('action', options.method.toUpperCase() + ' ' + options.url);
should(data).have.property('collection_name', options.log.collection); should(data).have.property('collection_name', options.log.collection);
if (options.log.hasOwnProperty('data')) { if (options.log.hasOwnProperty('data')) {
should(data).have.property('data', options.log.data); should(data).have.property('data', options.log.data);
} }
else { else {
const ignore = ['_id', '__v']; const ignore = ['_id', '__v'];
if (options.log.hasOwnProperty('dataIgn')) { if (options.log.hasOwnProperty('dataIgn')) {
ignore.push(...options.log.dataIgn); ignore.push(...options.log.dataIgn);
} }
let tmp = options.req ? options.req : {}; let tmp = options.req ? options.req : {};
if (options.log.hasOwnProperty('dataAdd')) { if (options.log.hasOwnProperty('dataAdd')) {
_.assign(tmp, options.log.dataAdd) _.assign(tmp, options.log.dataAdd)
} }
should(IdValidate.stringify(_.omit(data.data, ignore))).be.eql(_.omit(tmp, ignore)); should(IdValidate.stringify(_.omit(data.data, ignore))).be.eql(_.omit(tmp, ignore));
} }
if (data.user_id) { if (data.user_id) {
should(data.user_id.toString()).be.eql(this.auth[options.auth.basic].id); should(data.user_id.toString()).be.eql(this.auth[options.auth.basic].id);
} }
done(); done();
}); });
}); });
} }
else { // return object to do .end() manually else { // return object to do .end() manually
return st; return st;
} }
} }
static afterEach (server, done) { static afterEach (server, done) {
server.close(done); server.close(done);
} }
static after(done) { static after(done) {
db.disconnect(done); db.disconnect(done);
} }
} }

View File

@ -3,12 +3,12 @@ import db from '../db';
// script to load test db into dev db for a clean start // script to load test db into dev db for a clean start
db.connect('dev', () => { db.connect('dev', () => {
console.info('dropping data...'); console.info('dropping data...');
db.drop(() => { // reset database db.drop(() => { // reset database
console.info('loading data...'); console.info('loading data...');
db.loadJson(require('./db.json'), () => { db.loadJson(require('./db.json'), () => {
console.info('done'); console.info('done');
process.exit(0); process.exit(0);
}); });
}); });
}); });