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