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",
"version": "1.0.0",
"name": "definma-api",
"version": "0.9.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import mongoose from 'mongoose';
const ChangelogSchema = new mongoose.Schema({
action: String,
collection_name: String,
conditions: mongoose.Schema.Types.Mixed,
data: Object,
user_id: mongoose.Schema.Types.ObjectId
action: String,
collection_name: String,
conditions: mongoose.Schema.Types.Mixed,
data: Object,
user_id: mongoose.Schema.Types.ObjectId
}, {minimize: false, strict: false});
export default mongoose.model<any, mongoose.Model<any, any>>('changelog', ChangelogSchema);
export default mongoose.model<any, mongoose.Model<any, any>>('changelog', ChangelogSchema);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import mongoose from 'mongoose';
const ModelFileSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}},
data: Buffer
name: {type: String, index: {unique: true}},
data: Buffer
});
export default mongoose.model<any, mongoose.Model<any, any>>('model_file', ModelFileSchema);
export default mongoose.model<any, mongoose.Model<any, any>>('model_file', ModelFileSchema);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
// respond with 400 and include error details from the joi validation
export default function res400 (error, res) {
res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
}
res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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