Change first character of all end-of-line comments to upper case
This commit is contained in:
parent
a08bfd9922
commit
0006b09d8d
32
src/api.ts
32
src/api.ts
@ -4,22 +4,22 @@ import jsonRefParser, {JSONSchema} from '@apidevtools/json-schema-ref-parser';
|
|||||||
import oasParser from '@apidevtools/swagger-parser';
|
import oasParser from '@apidevtools/swagger-parser';
|
||||||
|
|
||||||
|
|
||||||
// modified from https://github.com/scottie1984/swagger-ui-express
|
// Modified from https://github.com/scottie1984/swagger-ui-express
|
||||||
// usage: app.use('/api-doc', api.serve(), api.setup());
|
// Usage: app.use('/api-doc', api.serve(), api.setup());
|
||||||
// the paths property can be split using allOf
|
// The paths property can be split using allOf
|
||||||
// further route documentation can be included in the x-doc property
|
// Further route documentation can be included in the x-doc property
|
||||||
|
|
||||||
|
|
||||||
export default function api () {
|
export default function api () {
|
||||||
// generate apiDoc
|
// Generate apiDoc
|
||||||
let apiDoc: JSONSchema = {};
|
let apiDoc: JSONSchema = {};
|
||||||
jsonRefParser.bundle('api/api.yaml', (err, doc) => { // parse yaml
|
jsonRefParser.bundle('api/api.yaml', (err, doc) => { // Parse yaml
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
apiDoc = doc;
|
apiDoc = doc;
|
||||||
apiDoc.servers.splice(process.env.NODE_ENV === 'production', 1);
|
apiDoc.servers.splice(process.env.NODE_ENV === 'production', 1);
|
||||||
apiDoc.paths = apiDoc.paths.allOf.reduce((s, e) => Object.assign(s, e)); // bundle routes
|
apiDoc.paths = apiDoc.paths.allOf.reduce((s, e) => Object.assign(s, e)); // Bundle routes
|
||||||
apiDoc = resolveXDoc(apiDoc);
|
apiDoc = resolveXDoc(apiDoc);
|
||||||
oasParser.validate(apiDoc, (err, api) => { // validate oas schema
|
oasParser.validate(apiDoc, (err, api) => { // Validate oas schema
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ export default function api () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
(req, res, next) => { // serve init js and apiDoc file
|
(req, res, next) => { // Serve init js and apiDoc file
|
||||||
switch (req.url) {
|
switch (req.url) {
|
||||||
case '/swagger-ui-init.js':
|
case '/swagger-ui-init.js':
|
||||||
res.set('Content-Type', 'application/javascript');
|
res.set('Content-Type', 'application/javascript');
|
||||||
@ -43,21 +43,21 @@ export default function api () {
|
|||||||
default:
|
default:
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
}, // serve swagger files
|
}, // Serve swagger files
|
||||||
express.static(swaggerUi.getAbsoluteFSPath(), {index: false}),
|
express.static(swaggerUi.getAbsoluteFSPath(), {index: false}),
|
||||||
(req, res) => { // serve html file as default
|
(req, res) => { // Serve html file as default
|
||||||
res.send(htmlTplString);
|
res.send(htmlTplString);
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function resolveXDoc (doc) { // resolve x-doc properties recursively
|
function resolveXDoc (doc) { // Resolve x-doc properties recursively
|
||||||
Object.keys(doc).forEach(key => {
|
Object.keys(doc).forEach(key => {
|
||||||
if (doc[key] !== null && doc[key].hasOwnProperty('x-doc')) { // add x-doc to description, is styled via css
|
if (doc[key] !== null && doc[key].hasOwnProperty('x-doc')) { // Add x-doc to description, is styled via css
|
||||||
doc[key].description += '<details class="docs"><summary>docs</summary>' + doc[key]['x-doc'] + '</details>';
|
doc[key].description += '<details class="docs"><summary>docs</summary>' + doc[key]['x-doc'] + '</details>';
|
||||||
}
|
}
|
||||||
else if (typeof doc[key] === 'object' && doc[key] !== null) { // go deeper into recursion
|
else if (typeof doc[key] === 'object' && doc[key] !== null) { // Go deeper into recursion
|
||||||
doc[key] = resolveXDoc(doc[key]);
|
doc[key] = resolveXDoc(doc[key]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -65,9 +65,9 @@ function resolveXDoc (doc) { // resolve x-doc properties recursively
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// templates
|
// Templates
|
||||||
|
|
||||||
// noinspection LongLine
|
// Noinspection LongLine
|
||||||
const htmlTplString = `
|
const htmlTplString = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
72
src/db.ts
72
src/db.ts
@ -5,29 +5,29 @@ import ChangelogModel from './models/changelog';
|
|||||||
import cron from 'node-cron';
|
import cron from 'node-cron';
|
||||||
|
|
||||||
|
|
||||||
// database urls, prod db url is retrieved automatically
|
// Database urls, prod db url is retrieved automatically
|
||||||
const TESTING_URL = 'mongodb://localhost/dfopdb_test';
|
const TESTING_URL = 'mongodb://localhost/dfopdb_test';
|
||||||
const DEV_URL = 'mongodb://localhost/dfopdb';
|
const DEV_URL = 'mongodb://localhost/dfopdb';
|
||||||
const debugging = true;
|
const debugging = true;
|
||||||
const changelogKeepDays = 30; // days to keep the changelog
|
const changelogKeepDays = 30; // Days to keep the changelog
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production' && debugging) {
|
if (process.env.NODE_ENV !== 'production' && debugging) {
|
||||||
mongoose.set('debug', true); // enable mongoose debug
|
mongoose.set('debug', true); // Enable mongoose debug
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class db {
|
export default class db {
|
||||||
private static state = { // db object and current mode (test, dev, prod)
|
private static state = { // Db object and current mode (test, dev, prod)
|
||||||
db: null,
|
db: null,
|
||||||
mode: null,
|
mode: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// set mode to test for unit/integration tests, otherwise skip parameters. done is also only needed for testing
|
// Set mode to test for unit/integration tests, otherwise skip parameters. done is also only needed for testing
|
||||||
static connect (mode = '', done: Function = () => {}) {
|
static connect (mode = '', done: Function = () => {}) {
|
||||||
if (this.state.db) return done(); // db is already connected
|
if (this.state.db) return done(); // Db is already connected
|
||||||
|
|
||||||
// find right connection url
|
// Find right connection url
|
||||||
let connectionString: string = "";
|
let connectionString: string = "";
|
||||||
if (mode === 'test') { // testing
|
if (mode === 'test') { // Testing
|
||||||
connectionString = TESTING_URL;
|
connectionString = TESTING_URL;
|
||||||
this.state.mode = 'test';
|
this.state.mode = 'test';
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ export default class db {
|
|||||||
this.state.mode = 'dev';
|
this.state.mode = 'dev';
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect to db
|
// Connect to db
|
||||||
mongoose.connect(connectionString, {
|
mongoose.connect(connectionString, {
|
||||||
useNewUrlParser: true,
|
useNewUrlParser: true,
|
||||||
useUnifiedTopology: true,
|
useUnifiedTopology: true,
|
||||||
@ -55,19 +55,19 @@ export default class db {
|
|||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
});
|
});
|
||||||
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
|
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
|
||||||
mongoose.connection.on('connected', () => { // evaluation connection behaviour on prod
|
mongoose.connection.on('connected', () => { // Evaluation connection behaviour on prod
|
||||||
if (process.env.NODE_ENV !== 'test') { // Do not interfere with testing
|
if (process.env.NODE_ENV !== 'test') { // Do not interfere with testing
|
||||||
console.info('Database connected');
|
console.info('Database connected');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mongoose.connection.on('disconnected', () => { // reset state on disconnect
|
mongoose.connection.on('disconnected', () => { // Reset state on disconnect
|
||||||
if (process.env.NODE_ENV !== 'test') { // Do not interfere with testing
|
if (process.env.NODE_ENV !== 'test') { // Do not interfere with testing
|
||||||
console.info('Database disconnected');
|
console.info('Database disconnected');
|
||||||
// this.state.db = 0; // prod database connects and disconnects automatically
|
// This.state.db = 0; // prod database connects and disconnects automatically
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
process.on('SIGINT', () => { // close connection when app is terminated
|
process.on('SIGINT', () => { // Close connection when app is terminated
|
||||||
if (!this.state.db) { // database still connected
|
if (!this.state.db) { // Database still connected
|
||||||
mongoose.connection.close(() => {
|
mongoose.connection.close(() => {
|
||||||
console.info('Mongoose default connection disconnected through app termination');
|
console.info('Mongoose default connection disconnected through app termination');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
@ -81,9 +81,9 @@ export default class db {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mode !== 'test') { // clear old changelog regularly
|
if (mode !== 'test') { // Clear old changelog regularly
|
||||||
cron.schedule('0 0 * * *', () => {
|
cron.schedule('0 0 * * *', () => {
|
||||||
ChangelogModel.deleteMany({_id: {$lt: // id from time
|
ChangelogModel.deleteMany({_id: {$lt: // Id from time
|
||||||
Math.floor(new Date().getTime() / 1000 - changelogKeepDays * 24 * 60 * 60).toString(16) + '0000000000000000'
|
Math.floor(new Date().getTime() / 1000 - changelogKeepDays * 24 * 60 * 60).toString(16) + '0000000000000000'
|
||||||
}}).lean().exec(err => {
|
}}).lean().exec(err => {
|
||||||
if (err) console.error(err);
|
if (err) console.error(err);
|
||||||
@ -104,18 +104,18 @@ export default class db {
|
|||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop all collections of connected db (only dev and test for safety reasons)
|
// Drop all collections of connected db (only dev and test for safety reasons)
|
||||||
static drop (done: Function = () => {}) {
|
static drop (done: Function = () => {}) {
|
||||||
if (!this.state.db || this.state.mode === 'prod') return done(); // no db connection or prod db
|
if (!this.state.db || this.state.mode === 'prod') return done(); // No db connection or prod db
|
||||||
this.state.db.db.listCollections().toArray((err, collections) => { // get list of all collections
|
this.state.db.db.listCollections().toArray((err, collections) => { // Get list of all collections
|
||||||
if (collections.length === 0) { // there are no collections to drop
|
if (collections.length === 0) { // There are no collections to drop
|
||||||
return done();
|
return done();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let dropCounter = 0; // count number of dropped collections to know when to return done()
|
let dropCounter = 0; // Count number of dropped collections to know when to return done()
|
||||||
collections.forEach(collection => { // drop each collection
|
collections.forEach(collection => { // Drop each collection
|
||||||
this.state.db.dropCollection(collection.name, () => {
|
this.state.db.dropCollection(collection.name, () => {
|
||||||
if (++ dropCounter >= collections.length) { // all collections dropped
|
if (++ dropCounter >= collections.length) { // All collections dropped
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -124,21 +124,21 @@ export default class db {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadJson (json, done: Function = () => {}) { // insert given JSON data into db, uses core mongodb methods
|
static loadJson (json, done: Function = () => {}) { // Insert given JSON data into db, uses core mongodb methods
|
||||||
// no db connection or nothing to load
|
// No db connection or nothing to load
|
||||||
if (!this.state.db || !json.hasOwnProperty('collections') || json.collections.length === 0) {
|
if (!this.state.db || !json.hasOwnProperty('collections') || json.collections.length === 0) {
|
||||||
return done();
|
return done();
|
||||||
}
|
}
|
||||||
|
|
||||||
let loadCounter = 0; // count number of loaded collections to know when to return done()
|
let loadCounter = 0; // Count number of loaded collections to know when to return done()
|
||||||
Object.keys(json.collections).forEach(collectionName => { // create each collection
|
Object.keys(json.collections).forEach(collectionName => { // Create each collection
|
||||||
json.collections[collectionName] = this.oidResolve(json.collections[collectionName]);
|
json.collections[collectionName] = this.oidResolve(json.collections[collectionName]);
|
||||||
this.state.db.createCollection(collectionName, (err, collection) => {
|
this.state.db.createCollection(collectionName, (err, collection) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
collection.insertMany(json.collections[collectionName], () => { // insert JSON data
|
collection.insertMany(json.collections[collectionName], () => { // Insert JSON data
|
||||||
if (++ loadCounter >= Object.keys(json.collections).length) { // all collections loaded
|
if (++ loadCounter >= Object.keys(json.collections).length) { // All collections loaded
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -146,11 +146,11 @@ export default class db {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// changelog entry, expects (req, this (from query helper)) or (req, collection, conditions, data)
|
// Changelog entry, expects (req, this (from query helper)) or (req, collection, conditions, data)
|
||||||
static log(req, thisOrCollection, conditions = null, data = null) {
|
static log(req, thisOrCollection, conditions = null, data = null) {
|
||||||
if (! (conditions || data)) { // (req, this)
|
if (! (conditions || data)) { // (req, this)
|
||||||
data = thisOrCollection._update ? _.cloneDeep(thisOrCollection._update) : {}; // replace undefined with {}
|
data = thisOrCollection._update ? _.cloneDeep(thisOrCollection._update) : {}; // Replace undefined with {}
|
||||||
// replace keys with a leading $
|
// Replace keys with a leading $
|
||||||
Object.keys(data).forEach(key => {
|
Object.keys(data).forEach(key => {
|
||||||
if (key[0] === '$') {
|
if (key[0] === '$') {
|
||||||
data[key.substr(1)] = data[key];
|
data[key.substr(1)] = data[key];
|
||||||
@ -180,19 +180,19 @@ export default class db {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static oidResolve (object: any) { // resolve $oid fields to actual ObjectIds recursively
|
private static oidResolve (object: any) { // Resolve $oid fields to actual ObjectIds recursively
|
||||||
Object.keys(object).forEach(key => {
|
Object.keys(object).forEach(key => {
|
||||||
if (object[key] !== null && object[key].hasOwnProperty('$oid')) { // found oid, replace
|
if (object[key] !== null && object[key].hasOwnProperty('$oid')) { // Found oid, replace
|
||||||
object[key] = mongoose.Types.ObjectId(object[key].$oid);
|
object[key] = mongoose.Types.ObjectId(object[key].$oid);
|
||||||
}
|
}
|
||||||
else if (typeof object[key] === 'object' && object[key] !== null) { // deeper into recursion
|
else if (typeof object[key] === 'object' && object[key] !== null) { // Deeper into recursion
|
||||||
object[key] = this.oidResolve(object[key]);
|
object[key] = this.oidResolve(object[key]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static logEscape(obj) { // replace MongoDB control characters in keys
|
private static logEscape(obj) { // Replace MongoDB control characters in keys
|
||||||
if (Object(obj) === obj && Object.keys(obj).length > 0) {
|
if (Object(obj) === obj && Object.keys(obj).length > 0) {
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach(key => {
|
||||||
const safeKey = key.replace(/[$.]/g, '');
|
const safeKey = key.replace(/[$.]/g, '');
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// globals for required names in the database. change values here to rename these properties
|
// Globals for required names in the database. change values here to rename these properties
|
||||||
// the keys are the terms used internally, the values can be changed to other terms used in database and output
|
// The keys are the terms used internally, the values can be changed to other terms used in database and output
|
||||||
|
|
||||||
const globals = {
|
const globals = {
|
||||||
levels: { // access levels, sorted asc by rights
|
levels: { // Access levels, sorted asc by rights
|
||||||
predict: 'predict',
|
predict: 'predict',
|
||||||
read: 'read',
|
read: 'read',
|
||||||
write: 'write',
|
write: 'write',
|
||||||
@ -10,13 +10,13 @@ const globals = {
|
|||||||
admin: 'admin'
|
admin: 'admin'
|
||||||
},
|
},
|
||||||
|
|
||||||
status: { // names of the document statuses
|
status: { // Names of the document statuses
|
||||||
del: 'deleted',
|
del: 'deleted',
|
||||||
new: 'new',
|
new: 'new',
|
||||||
val: 'validated',
|
val: 'validated',
|
||||||
},
|
},
|
||||||
|
|
||||||
spectrum: { // names of required spectrum fields
|
spectrum: { // Names of required spectrum fields
|
||||||
spectrum: 'spectrum',
|
spectrum: 'spectrum',
|
||||||
dpt: 'dpt'
|
dpt: 'dpt'
|
||||||
}
|
}
|
||||||
|
@ -4,22 +4,22 @@ import UserModel from '../models/user';
|
|||||||
import globals from '../globals';
|
import globals from '../globals';
|
||||||
|
|
||||||
|
|
||||||
// appends req.auth(res, ['levels'], method = 'all')
|
// Appends req.auth(res, ['levels'], method = 'all')
|
||||||
// which returns sends error message and returns false if unauthorized, otherwise true
|
// Which returns sends error message and returns false if unauthorized, otherwise true
|
||||||
// req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'}
|
// Req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'}
|
||||||
|
|
||||||
module.exports = async (req, res, next) => {
|
module.exports = async (req, res, next) => {
|
||||||
let givenMethod = ''; // authorization method given by client, basic taken preferred
|
let givenMethod = ''; // Authorization method given by client, basic taken preferred
|
||||||
let user = {name: '', level: '', id: '', location: '', models: []}; // user object
|
let user = {name: '', level: '', id: '', location: '', models: []}; // User object
|
||||||
|
|
||||||
// test authentications
|
// Test authentications
|
||||||
const userBasic = await basic(req, next);
|
const userBasic = await basic(req, next);
|
||||||
|
|
||||||
if (userBasic) { // basic available
|
if (userBasic) { // Basic available
|
||||||
givenMethod = 'basic';
|
givenMethod = 'basic';
|
||||||
user = userBasic;
|
user = userBasic;
|
||||||
}
|
}
|
||||||
else { // if basic not available, test key
|
else { // If basic not available, test key
|
||||||
const userKey = await key(req, next);
|
const userKey = await key(req, next);
|
||||||
if (userKey) {
|
if (userKey) {
|
||||||
givenMethod = 'key';
|
givenMethod = 'key';
|
||||||
@ -28,8 +28,8 @@ module.exports = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.auth = (res, levels, method = 'all') => {
|
req.auth = (res, levels, method = 'all') => {
|
||||||
if (givenMethod === method || (method === 'all' && givenMethod !== '')) { // method is available
|
if (givenMethod === method || (method === 'all' && givenMethod !== '')) { // Method is available
|
||||||
if (levels.indexOf(user.level) > -1) { // level is available
|
if (levels.indexOf(user.level) > -1) { // Level is available
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -56,16 +56,16 @@ module.exports = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function basic (req, next): any { // checks basic auth and returns changed user object
|
function basic (req, next): any { // Checks basic auth and returns changed user object
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const auth = basicAuth(req);
|
const auth = basicAuth(req);
|
||||||
if (auth !== undefined) { // basic auth available
|
if (auth !== undefined) { // Basic auth available
|
||||||
UserModel.find({name: auth.name, status: globals.status.new}).lean().exec( (err, data: any) => { // find user
|
UserModel.find({name: auth.name, status: globals.status.new}).lean().exec( (err, data: any) => { // Find user
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data.length === 1) { // one user found
|
if (data.length === 1) { // One user found
|
||||||
bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // check password
|
bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // Check password
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (res === true) { // password correct
|
if (res === true) { // Password correct
|
||||||
resolve({
|
resolve({
|
||||||
level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
|
level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
|
||||||
name: data[0].name,
|
name: data[0].name,
|
||||||
@ -90,12 +90,12 @@ function basic (req, next): any { // checks basic auth and returns changed user
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function key (req, next): any { // checks API key and returns changed user object
|
function key (req, next): any { // Checks API key and returns changed user object
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (req.query.key !== undefined) { // key available
|
if (req.query.key !== undefined) { // Key available
|
||||||
UserModel.find({key: req.query.key, status: globals.status.new}).lean().exec( (err, data: any) => { // find user
|
UserModel.find({key: req.query.key, status: globals.status.new}).lean().exec( (err, data: any) => { // Find user
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data.length === 1) { // one user found
|
if (data.length === 1) { // One user found
|
||||||
resolve({
|
resolve({
|
||||||
level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
|
level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
|
||||||
name: data[0].name,
|
name: data[0].name,
|
||||||
@ -104,7 +104,7 @@ function key (req, next): any { // checks API key and returns changed user obje
|
|||||||
models: data[0].models
|
models: data[0].models
|
||||||
});
|
});
|
||||||
if (!/^\/api/m.test(req.url)){
|
if (!/^\/api/m.test(req.url)){
|
||||||
delete req.query.key; // delete query parameter to avoid interference with later validation
|
delete req.query.key; // Delete query parameter to avoid interference with later validation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {parseAsync} from 'json2csv';
|
import {parseAsync} from 'json2csv';
|
||||||
import flatten from './flatten';
|
import flatten from './flatten';
|
||||||
|
|
||||||
export default function csv(input: any[], f: (err, data) => void) { // parse JSON to CSV
|
export default function csv(input: any[], f: (err, data) => void) { // Parse JSON to CSV
|
||||||
parseAsync(input.map(e => flatten(e)), {includeEmptyRows: true})
|
parseAsync(input.map(e => flatten(e)), {includeEmptyRows: true})
|
||||||
.then(csv => f(null, csv))
|
.then(csv => f(null, csv))
|
||||||
.catch(err => f(err, null));
|
.catch(err => f(err, null));
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import globals from '../globals';
|
import globals from '../globals';
|
||||||
|
|
||||||
export default function flatten (data, keepArray = false) { // flatten object: {a: {b: true}} -> {a.b: true}
|
export default function flatten (data, keepArray = false) { // Flatten object: {a: {b: true}} -> {a.b: true}
|
||||||
const result = {};
|
const result = {};
|
||||||
function recurse (cur, prop) {
|
function recurse (cur, prop) {
|
||||||
if (Object(cur) !== cur || Object.keys(cur).length === 0) { // simple value
|
if (Object(cur) !== cur || Object.keys(cur).length === 0) { // Simple value
|
||||||
result[prop] = cur;
|
result[prop] = cur;
|
||||||
}
|
}
|
||||||
else if (prop === `${globals.spectrum.spectrum}.${globals.spectrum.dpt}`) { // convert spectrum for ML
|
else if (prop === `${globals.spectrum.spectrum}.${globals.spectrum.dpt}`) { // Convert spectrum for ML
|
||||||
result[prop + '.labels'] = cur.map(e => parseFloat(e[0]));
|
result[prop + '.labels'] = cur.map(e => parseFloat(e[0]));
|
||||||
result[prop + '.values'] = cur.map(e => parseFloat(e[1]));
|
result[prop + '.values'] = cur.map(e => parseFloat(e[1]));
|
||||||
}
|
}
|
||||||
@ -14,8 +14,8 @@ export default function flatten (data, keepArray = false) { // flatten object:
|
|||||||
if (keepArray) {
|
if (keepArray) {
|
||||||
result[prop] = cur;
|
result[prop] = cur;
|
||||||
}
|
}
|
||||||
else { // array to string
|
else { // Array to string
|
||||||
if (cur.length && (Object(cur[0]) !== cur || Object.keys(cur[0]).length === 0)) { // array of non-objects
|
if (cur.length && (Object(cur[0]) !== cur || Object.keys(cur[0]).length === 0)) { // Array of non-objects
|
||||||
result[prop] = '[' + cur.join(', ') + ']';
|
result[prop] = '[' + cur.join(', ') + ']';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -27,7 +27,7 @@ export default function flatten (data, keepArray = false) { // flatten object:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // object
|
else { // Object
|
||||||
let isEmpty = true;
|
let isEmpty = true;
|
||||||
for (let p in cur) {
|
for (let p in cur) {
|
||||||
isEmpty = false;
|
isEmpty = false;
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
// sends an email using the BIC service
|
// Sends an email using the BIC service
|
||||||
|
|
||||||
export default class Mail{
|
export default class Mail{
|
||||||
|
|
||||||
static readonly address = 'definma@bosch-iot.com'; // email address
|
static readonly address = 'definma@bosch-iot.com'; // Email address
|
||||||
static uri: string; // mail API URI
|
static uri: string; // Mail API URI
|
||||||
static auth = {username: '', password: ''}; // mail API credentials
|
static auth = {username: '', password: ''}; // Mail API credentials
|
||||||
static mailPass: string; // mail API generates password
|
static mailPass: string; // Mail API generates password
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
if (process.env.NODE_ENV === 'production') { // only send mails in production
|
if (process.env.NODE_ENV === 'production') { // Only send mails in production
|
||||||
this.mailPass = Array(64).fill(0).map(() => Math.floor(Math.random() * 10)).join('');
|
this.mailPass = Array(64).fill(0).map(() => Math.floor(Math.random() * 10)).join('');
|
||||||
this.uri = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.uri;
|
this.uri = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.uri;
|
||||||
this.auth.username = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.username;
|
this.auth.username = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.username;
|
||||||
this.auth.password = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.password;
|
this.auth.password = JSON.parse(process.env.VCAP_SERVICES).Mail[0].credentials.password;
|
||||||
axios({ // get registered mail addresses
|
axios({ // Get registered mail addresses
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: this.uri + '/management/userDomainMapping',
|
url: this.uri + '/management/userDomainMapping',
|
||||||
auth: this.auth
|
auth: this.auth
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (res.data.addresses.indexOf(this.address) < 0) { // mail address not registered
|
if (res.data.addresses.indexOf(this.address) < 0) { // Mail address not registered
|
||||||
if (res.data.addresses.length) { // delete wrong registered mail address
|
if (res.data.addresses.length) { // Delete wrong registered mail address
|
||||||
await axios({
|
await axios({
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
url: this.uri + '/management/mailAddresses/' + res.data.addresses[0],
|
url: this.uri + '/management/mailAddresses/' + res.data.addresses[0],
|
||||||
auth: this.auth
|
auth: this.auth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await axios({ // register right mail address
|
await axios({ // Register right mail address
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: this.uri + '/management/mailAddresses/' + this.address,
|
url: this.uri + '/management/mailAddresses/' + this.address,
|
||||||
auth: this.auth
|
auth: this.auth
|
||||||
@ -43,22 +43,22 @@ export default class Mail{
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return axios({ // set new mail password
|
return axios({ // Set new mail password
|
||||||
method: 'put',
|
method: 'put',
|
||||||
url: this.uri + '/management/mailAddresses/' + this.address + '/password/' + this.mailPass,
|
url: this.uri + '/management/mailAddresses/' + this.address + '/password/' + this.mailPass,
|
||||||
auth: this.auth
|
auth: this.auth
|
||||||
});
|
});
|
||||||
}).then(() => { // init done successfully
|
}).then(() => { // Init done successfully
|
||||||
console.info('Mail service established successfully');
|
console.info('Mail service established successfully');
|
||||||
}).catch(err => { // somewhere an error occurred
|
}).catch(err => { // Somewhere an error occurred
|
||||||
console.error(`Mail init error: ${err.request.method} ${err.request.path}: ${err.response.status}`,
|
console.error(`Mail init error: ${err.request.method} ${err.request.path}: ${err.response.status}`,
|
||||||
err.response.data);
|
err.response.data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static send (mailAddress, subject, content, f: (x?) => void = () => {}) { // callback executed empty or with error
|
static send (mailAddress, subject, content, f: (x?) => void = () => {}) { // Callback executed empty or with error
|
||||||
if (process.env.NODE_ENV === 'production') { // only send mails in production
|
if (process.env.NODE_ENV === 'production') { // Only send mails in production
|
||||||
axios({
|
axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: this.uri + '/email',
|
url: this.uri + '/email',
|
||||||
@ -81,7 +81,7 @@ export default class Mail{
|
|||||||
f(err);
|
f(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else { // dev dummy replacement
|
else { // Dev dummy replacement
|
||||||
console.info('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
|
console.info('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
|
||||||
f();
|
f();
|
||||||
}
|
}
|
||||||
|
42
src/index.ts
42
src/index.ts
@ -9,24 +9,24 @@ import db from './db';
|
|||||||
import Mail from './helpers/mail';
|
import Mail from './helpers/mail';
|
||||||
|
|
||||||
|
|
||||||
// tell if server is running in debug or production environment
|
// Tell if server is running in debug or production environment
|
||||||
console.info(process.env.NODE_ENV === 'production' ?
|
console.info(process.env.NODE_ENV === 'production' ?
|
||||||
'===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
|
'===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
|
||||||
|
|
||||||
|
|
||||||
// mongodb connection
|
// Mongodb connection
|
||||||
db.connect();
|
db.connect();
|
||||||
|
|
||||||
// mail service
|
// Mail service
|
||||||
Mail.init();
|
Mail.init();
|
||||||
|
|
||||||
// create Express app
|
// Create Express app
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// get port from environment, defaults to 3000
|
// Get port from environment, defaults to 3000
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
// security headers
|
// Security headers
|
||||||
const defaultHeaderConfig = {
|
const defaultHeaderConfig = {
|
||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
directives: {
|
directives: {
|
||||||
@ -43,7 +43,7 @@ const defaultHeaderConfig = {
|
|||||||
refererPolicy: true
|
refererPolicy: true
|
||||||
};
|
};
|
||||||
app.use(helmet(defaultHeaderConfig));
|
app.use(helmet(defaultHeaderConfig));
|
||||||
// special CSP header for api-doc
|
// Special CSP header for api-doc
|
||||||
app.use('/api-doc', helmet.contentSecurityPolicy({
|
app.use('/api-doc', helmet.contentSecurityPolicy({
|
||||||
...defaultHeaderConfig,
|
...defaultHeaderConfig,
|
||||||
directives: {
|
directives: {
|
||||||
@ -54,7 +54,7 @@ app.use('/api-doc', helmet.contentSecurityPolicy({
|
|||||||
imgSrc: [`'self'`, 'data:']
|
imgSrc: [`'self'`, 'data:']
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
// special CSP header for the intro-presentation
|
// Special CSP header for the intro-presentation
|
||||||
app.use(/\/static\/intro-presentation\/(index.html)?/, helmet.contentSecurityPolicy({
|
app.use(/\/static\/intro-presentation\/(index.html)?/, helmet.contentSecurityPolicy({
|
||||||
...defaultHeaderConfig,
|
...defaultHeaderConfig,
|
||||||
directives: {
|
directives: {
|
||||||
@ -64,7 +64,7 @@ app.use(/\/static\/intro-presentation\/(index.html)?/, helmet.contentSecurityPol
|
|||||||
imgSrc: [`'self'`]
|
imgSrc: [`'self'`]
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
// special CSP header for the bosch-logo.svg
|
// Special CSP header for the bosch-logo.svg
|
||||||
app.use('/static/*.svg', helmet.contentSecurityPolicy({
|
app.use('/static/*.svg', helmet.contentSecurityPolicy({
|
||||||
...defaultHeaderConfig,
|
...defaultHeaderConfig,
|
||||||
directives: {
|
directives: {
|
||||||
@ -72,8 +72,8 @@ app.use('/static/*.svg', helmet.contentSecurityPolicy({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// middleware
|
// Middleware
|
||||||
app.use(compression()); // compress responses
|
app.use(compression()); // Compress responses
|
||||||
app.use(express.json({ limit: '5mb'}));
|
app.use(express.json({ limit: '5mb'}));
|
||||||
app.use(express.urlencoded({ extended: false, limit: '5mb' }));
|
app.use(express.urlencoded({ extended: false, limit: '5mb' }));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
@ -81,11 +81,11 @@ app.use(contentFilter({
|
|||||||
urlBlackList: ['$', '&&', '||'],
|
urlBlackList: ['$', '&&', '||'],
|
||||||
bodyBlackList: ['$', '{', '&&', '||'],
|
bodyBlackList: ['$', '{', '&&', '||'],
|
||||||
appendFound: true
|
appendFound: true
|
||||||
})); // filter URL query attacks
|
})); // Filter URL query attacks
|
||||||
app.use((err, req, res, ignore) => { // bodyParser error handling
|
app.use((err, req, res, ignore) => { // BodyParser error handling
|
||||||
res.status(400).send({status: 'Invalid JSON body'});
|
res.status(400).send({status: 'Invalid JSON body'});
|
||||||
});
|
});
|
||||||
app.use((req, res, next) => { // no database connection error
|
app.use((req, res, next) => { // No database connection error
|
||||||
if (db.getState().db) {
|
if (db.getState().db) {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
@ -95,12 +95,12 @@ app.use((req, res, next) => { // no database connection error
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.use(cors()); // CORS headers
|
app.use(cors()); // CORS headers
|
||||||
app.use(require('./helpers/authorize')); // handle authentication
|
app.use(require('./helpers/authorize')); // Handle authentication
|
||||||
|
|
||||||
// redirect /api routes for Angular proxy in development
|
// Redirect /api routes for Angular proxy in development
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
app.use('/api/:url([^]+)', (req, res) => {
|
app.use('/api/:url([^]+)', (req, res) => {
|
||||||
if (/help\//.test(req.params.url)) { // encode URI again for help route
|
if (/help\//.test(req.params.url)) { // Encode URI again for help route
|
||||||
req.params.url = 'help/' + encodeURIComponent(req.params.url.replace('help/', ''));
|
req.params.url = 'help/' + encodeURIComponent(req.params.url.replace('help/', ''));
|
||||||
}
|
}
|
||||||
req.url = '/' + req.params.url;
|
req.url = '/' + req.params.url;
|
||||||
@ -109,7 +109,7 @@ if (process.env.NODE_ENV !== 'production') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// require routes
|
// Require routes
|
||||||
app.use('/', require('./routes/root'));
|
app.use('/', require('./routes/root'));
|
||||||
app.use('/', require('./routes/sample'));
|
app.use('/', require('./routes/sample'));
|
||||||
app.use('/', require('./routes/material'));
|
app.use('/', require('./routes/material'));
|
||||||
@ -119,7 +119,7 @@ app.use('/', require('./routes/model'));
|
|||||||
app.use('/', require('./routes/user'));
|
app.use('/', require('./routes/user'));
|
||||||
app.use('/', require('./routes/help'));
|
app.use('/', require('./routes/help'));
|
||||||
|
|
||||||
// static files
|
// Static files
|
||||||
app.use('/static', express.static('static'));
|
app.use('/static', express.static('static'));
|
||||||
|
|
||||||
// Swagger UI
|
// Swagger UI
|
||||||
@ -129,13 +129,13 @@ app.use((req, res) => { // 404 error handling
|
|||||||
res.status(404).json({status: 'Not found'});
|
res.status(404).json({status: 'Not found'});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use((err, req, res, ignore) => { // internal server error handling
|
app.use((err, req, res, ignore) => { // Internal server error handling
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(500).json({status: 'Internal server error'});
|
res.status(500).json({status: 'Internal server error'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// hook up server to port
|
// Hook up server to port
|
||||||
const server = app.listen(port, () => {
|
const server = app.listen(port, () => {
|
||||||
console.info(process.env.NODE_ENV === 'test' ? '' : `Listening on http://localhost:${port}`);
|
console.info(process.env.NODE_ENV === 'test' ? '' : `Listening on http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
|
@ -9,9 +9,9 @@ const ConditionTemplateSchema = new mongoose.Schema({
|
|||||||
name: String,
|
name: String,
|
||||||
range: mongoose.Schema.Types.Mixed
|
range: mongoose.Schema.Types.Mixed
|
||||||
} ,{ _id : false })]
|
} ,{ _id : false })]
|
||||||
}, {minimize: false}); // to allow empty objects
|
}, {minimize: false}); // To allow empty objects
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
ConditionTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
ConditionTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -7,7 +7,7 @@ const HelpSchema = new mongoose.Schema({
|
|||||||
text: String
|
text: String
|
||||||
}, {minimize: false});
|
}, {minimize: false});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
HelpSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
HelpSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -12,7 +12,7 @@ const MaterialSchema = new mongoose.Schema({
|
|||||||
status: String
|
status: String
|
||||||
}, {minimize: false});
|
}, {minimize: false});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
MaterialSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
MaterialSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -5,7 +5,7 @@ const MaterialGroupsSchema = new mongoose.Schema({
|
|||||||
name: {type: String, index: {unique: true}}
|
name: {type: String, index: {unique: true}}
|
||||||
});
|
});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
MaterialGroupsSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
MaterialGroupsSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -5,7 +5,7 @@ const MaterialSuppliersSchema = new mongoose.Schema({
|
|||||||
name: {type: String, index: {unique: true}}
|
name: {type: String, index: {unique: true}}
|
||||||
});
|
});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
MaterialSuppliersSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
MaterialSuppliersSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -9,9 +9,9 @@ const MaterialTemplateSchema = new mongoose.Schema({
|
|||||||
name: String,
|
name: String,
|
||||||
range: mongoose.Schema.Types.Mixed
|
range: mongoose.Schema.Types.Mixed
|
||||||
} ,{ _id : false })]
|
} ,{ _id : false })]
|
||||||
}, {minimize: false}); // to allow empty objects
|
}, {minimize: false}); // To allow empty objects
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
MaterialTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
MaterialTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -12,7 +12,7 @@ const MeasurementSchema = new mongoose.Schema({
|
|||||||
status: String
|
status: String
|
||||||
}, {minimize: false});
|
}, {minimize: false});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
MeasurementSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
MeasurementSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -9,9 +9,9 @@ const MeasurementTemplateSchema = new mongoose.Schema({
|
|||||||
name: String,
|
name: String,
|
||||||
range: mongoose.Schema.Types.Mixed
|
range: mongoose.Schema.Types.Mixed
|
||||||
} ,{ _id : false })]
|
} ,{ _id : false })]
|
||||||
}, {minimize: false}); // to allow empty objects
|
}, {minimize: false}); // To allow empty objects
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
MeasurementTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
MeasurementTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -10,7 +10,7 @@ const ModelSchema = new mongoose.Schema({
|
|||||||
} ,{ _id : true })]
|
} ,{ _id : true })]
|
||||||
});
|
});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
ModelSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
ModelSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -10,7 +10,7 @@ const NoteSchema = new mongoose.Schema({
|
|||||||
custom_fields: mongoose.Schema.Types.Mixed
|
custom_fields: mongoose.Schema.Types.Mixed
|
||||||
});
|
});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
NoteSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
NoteSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -6,7 +6,7 @@ const NoteFieldSchema = new mongoose.Schema({
|
|||||||
qty: Number
|
qty: Number
|
||||||
});
|
});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
NoteFieldSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
NoteFieldSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -17,7 +17,7 @@ const SampleSchema = new mongoose.Schema({
|
|||||||
status: String
|
status: String
|
||||||
}, {minimize: false});
|
}, {minimize: false});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
SampleSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
SampleSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -14,7 +14,7 @@ const UserSchema = new mongoose.Schema({
|
|||||||
status: String
|
status: String
|
||||||
});
|
});
|
||||||
|
|
||||||
// changelog query helper
|
// Changelog query helper
|
||||||
UserSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
UserSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
|
||||||
db.log(req, this);
|
db.log(req, this);
|
||||||
return this;
|
return this;
|
||||||
|
@ -16,7 +16,7 @@ router.get('/help/:key', (req, res, next) => {
|
|||||||
if (!data) {
|
if (!data) {
|
||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
if (data.level !== 'none') { // check level
|
if (data.level !== 'none') { // Check level
|
||||||
if (!req.auth(res,
|
if (!req.auth(res,
|
||||||
Object.values(globals.levels).slice(Object.values(globals.levels).findIndex(e => e === data.level))
|
Object.values(globals.levels).slice(Object.values(globals.levels).findIndex(e => e === data.level))
|
||||||
, 'basic')) return;
|
, 'basic')) return;
|
||||||
|
@ -29,7 +29,7 @@ router.get('/materials', (req, res, next) => {
|
|||||||
.lean().exec((err, data) => {
|
.lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => MaterialValidate.output(e, true))));
|
res.json(_.compact(data.map(e => MaterialValidate.output(e, true))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -41,7 +41,7 @@ router.get(`/materials/:state(${globals.status.new}|${globals.status.del})`, (re
|
|||||||
.lean().exec((err, data) => {
|
.lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => MaterialValidate.output(e))));
|
res.json(_.compact(data.map(e => MaterialValidate.output(e))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -56,7 +56,7 @@ router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleted materials only available for dev/admin
|
// Deleted materials only available for dev/admin
|
||||||
if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
|
if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
|
||||||
res.json(MaterialValidate.output(data));
|
res.json(MaterialValidate.output(data));
|
||||||
});
|
});
|
||||||
@ -91,9 +91,9 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
materialData.properties.material_template.toString() !== material.properties.material_template)) return;
|
materialData.properties.material_template.toString() !== material.properties.material_template)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for changes
|
// Check for changes
|
||||||
if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), IdValidate.stringify(material))) {
|
if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), IdValidate.stringify(material))) {
|
||||||
material.status = globals.status.new; // set status to new
|
material.status = globals.status.new; // Set status to new
|
||||||
}
|
}
|
||||||
|
|
||||||
await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true})
|
await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true})
|
||||||
@ -107,7 +107,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
|
router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||||
if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
// check if there are still samples referencing this material
|
// Check if there are still samples referencing this material
|
||||||
SampleModel.find({'material_id': new mongoose.Types.ObjectId(req.params.id), status: {$ne: globals.status.del}})
|
SampleModel.find({'material_id': new mongoose.Types.ObjectId(req.params.id), status: {$ne: globals.status.del}})
|
||||||
.lean().exec((err, data) => {
|
.lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
@ -152,7 +152,7 @@ router.post('/material/new', async (req, res, next) => {
|
|||||||
if (!material) return;
|
if (!material) return;
|
||||||
if (!await propertiesCheck(material.properties, 'new', res, next)) return;
|
if (!await propertiesCheck(material.properties, 'new', res, next)) return;
|
||||||
|
|
||||||
material.status = globals.status.new; // set status to new
|
material.status = globals.status.new; // Set status to new
|
||||||
await new MaterialModel(material).save(async (err, data) => {
|
await new MaterialModel(material).save(async (err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
db.log(req, 'materials', {_id: data._id}, data.toObject());
|
db.log(req, 'materials', {_id: data._id}, data.toObject());
|
||||||
@ -168,7 +168,7 @@ router.get('/material/groups', (req, res, next) => {
|
|||||||
MaterialGroupModel.find().lean().exec((err, data: any) => {
|
MaterialGroupModel.find().lean().exec((err, data: any) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name))));
|
res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -179,7 +179,7 @@ router.get('/material/suppliers', (req, res, next) => {
|
|||||||
MaterialSupplierModel.find().lean().exec((err, data: any) => {
|
MaterialSupplierModel.find().lean().exec((err, data: any) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => MaterialValidate.outputSuppliers(e.name))));
|
res.json(_.compact(data.map(e => MaterialValidate.outputSuppliers(e.name))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -188,10 +188,10 @@ router.get('/material/suppliers', (req, res, next) => {
|
|||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
|
|
||||||
async function nameCheck (material, res, next) { // check if name was already taken
|
async function nameCheck (material, res, next) { // Check if name was already taken
|
||||||
const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => next(err)) as any;
|
const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => next(err)) as any;
|
||||||
if (materialData instanceof Error) return false;
|
if (materialData instanceof Error) return false;
|
||||||
if (materialData) { // could not find material_id
|
if (materialData) { // Could not find material_id
|
||||||
res.status(400).json({status: 'Material name already taken'});
|
res.status(400).json({status: 'Material name already taken'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -222,32 +222,32 @@ async function supplierResolve (material, req, next) {
|
|||||||
return material;
|
return material;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate material properties, returns false if invalid, otherwise template data
|
// Validate material properties, returns false if invalid, otherwise template data
|
||||||
async function propertiesCheck (properties, param, res, next, checkVersion = true) {
|
async function propertiesCheck (properties, param, res, next, checkVersion = true) {
|
||||||
if (!properties.material_template || !IdValidate.valid(properties.material_template)) { // template id not found
|
if (!properties.material_template || !IdValidate.valid(properties.material_template)) { // Template id not found
|
||||||
res.status(400).json({status: 'Material template not available'});
|
res.status(400).json({status: 'Material template not available'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const materialData = await MaterialTemplateModel.findById(properties.material_template)
|
const materialData = await MaterialTemplateModel.findById(properties.material_template)
|
||||||
.lean().exec().catch(err => next(err)) as any;
|
.lean().exec().catch(err => next(err)) as any;
|
||||||
if (materialData instanceof Error) return false;
|
if (materialData instanceof Error) return false;
|
||||||
if (!materialData) { // template not found
|
if (!materialData) { // Template not found
|
||||||
res.status(400).json({status: 'Material template not available'});
|
res.status(400).json({status: 'Material template not available'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkVersion) {
|
if (checkVersion) {
|
||||||
// get all template versions and check if given is latest
|
// Get all template versions and check if given is latest
|
||||||
const materialVersions = await MaterialTemplateModel.find({first_id: materialData.first_id}).sort({version: -1})
|
const materialVersions = await MaterialTemplateModel.find({first_id: materialData.first_id}).sort({version: -1})
|
||||||
.lean().exec().catch(err => next(err)) as any;
|
.lean().exec().catch(err => next(err)) as any;
|
||||||
if (materialVersions instanceof Error) return false;
|
if (materialVersions instanceof Error) return false;
|
||||||
if (properties.material_template !== materialVersions[0]._id.toString()) { // template not latest
|
if (properties.material_template !== materialVersions[0]._id.toString()) { // Template not latest
|
||||||
res.status(400).json({status: 'Old template version not allowed'});
|
res.status(400).json({status: 'Old template version not allowed'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate parameters
|
// Validate parameters
|
||||||
const {error, value} = ParametersValidate
|
const {error, value} = ParametersValidate
|
||||||
.input(_.omit(properties, 'material_template'), materialData.parameters, param);
|
.input(_.omit(properties, 'material_template'), materialData.parameters, param);
|
||||||
if (error) {res400(error, res); return false;}
|
if (error) {res400(error, res); return false;}
|
||||||
@ -257,7 +257,7 @@ async function propertiesCheck (properties, param, res, next, checkVersion = tru
|
|||||||
return materialData;
|
return materialData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStatus (status, req, res, next) { // set measurement status
|
function setStatus (status, req, res, next) { // Set measurement status
|
||||||
MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
|
MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ router.get('/measurement/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
if (!data) {
|
if (!data) {
|
||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
// deleted measurements only available for dev/admin
|
// Deleted measurements only available for dev/admin
|
||||||
if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
|
if (data.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
|
||||||
|
|
||||||
res.json(MeasurementValidate.output(data, req));
|
res.json(MeasurementValidate.output(data, req));
|
||||||
@ -45,16 +45,16 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
|
|||||||
return res.status(403).json({status: 'Forbidden'});
|
return res.status(403).json({status: 'Forbidden'});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add properties needed for sampleIdCheck
|
// Add properties needed for sampleIdCheck
|
||||||
measurement.measurement_template = data.measurement_template;
|
measurement.measurement_template = data.measurement_template;
|
||||||
measurement.sample_id = data.sample_id;
|
measurement.sample_id = data.sample_id;
|
||||||
if (!await sampleIdCheck(measurement, req, res, next)) return;
|
if (!await sampleIdCheck(measurement, req, res, next)) return;
|
||||||
|
|
||||||
// check for changes
|
// Check for changes
|
||||||
if (measurement.values) { // fill not changed values from database
|
if (measurement.values) { // Fill not changed values from database
|
||||||
measurement.values = _.assign({}, data.values, measurement.values);
|
measurement.values = _.assign({}, data.values, measurement.values);
|
||||||
if (!_.isEqual(measurement.values, data.values)) {
|
if (!_.isEqual(measurement.values, data.values)) {
|
||||||
measurement.status = globals.status.new; // set status to new
|
measurement.status = globals.status.new; // Set status to new
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,35 +130,35 @@ router.post('/measurement/new', async (req, res, next) => {
|
|||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
|
|
||||||
// validate sample_id, returns false if invalid or user has no access for this sample
|
// Validate sample_id, returns false if invalid or user has no access for this sample
|
||||||
async function sampleIdCheck (measurement, req, res, next) {
|
async function sampleIdCheck (measurement, req, res, next) {
|
||||||
const sampleData = await SampleModel.findById(measurement.sample_id)
|
const sampleData = await SampleModel.findById(measurement.sample_id)
|
||||||
.lean().exec().catch(err => {next(err); return false;}) as any;
|
.lean().exec().catch(err => {next(err); return false;}) as any;
|
||||||
if (!sampleData) { // sample_id not found
|
if (!sampleData) { // Sample_id not found
|
||||||
res.status(400).json({status: 'Sample id not available'});
|
res.status(400).json({status: 'Sample id not available'});
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// sample does not belong to user
|
// Sample does not belong to user
|
||||||
return !(sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic'));
|
return !(sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate measurement_template and values, returns values, true if values are {} or false if invalid,
|
// Validate measurement_template and values, returns values, true if values are {} or false if invalid,
|
||||||
// param for 'new'/'change'
|
// Param for 'new'/'change'
|
||||||
async function templateCheck (measurement, param, res, next) {
|
async function templateCheck (measurement, param, res, next) {
|
||||||
const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template)
|
const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template)
|
||||||
.lean().exec().catch(err => {next(err); return false;}) as any;
|
.lean().exec().catch(err => {next(err); return false;}) as any;
|
||||||
if (!templateData) { // template not found
|
if (!templateData) { // Template not found
|
||||||
res.status(400).json({status: 'Measurement template not available'});
|
res.status(400).json({status: 'Measurement template not available'});
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill not given values for new measurements
|
// Fill not given values for new measurements
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
// get all template versions and check if given is latest
|
// Get all template versions and check if given is latest
|
||||||
const templateVersions = await MeasurementTemplateModel.find({first_id: templateData.first_id}).sort({version: -1})
|
const templateVersions = await MeasurementTemplateModel.find({first_id: templateData.first_id}).sort({version: -1})
|
||||||
.lean().exec().catch(err => next(err)) as any;
|
.lean().exec().catch(err => next(err)) as any;
|
||||||
if (templateVersions instanceof Error) return false;
|
if (templateVersions instanceof Error) return false;
|
||||||
if (measurement.measurement_template !== templateVersions[0]._id.toString()) { // template not latest
|
if (measurement.measurement_template !== templateVersions[0]._id.toString()) { // Template not latest
|
||||||
res.status(400).json({status: 'Old template version not allowed'});
|
res.status(400).json({status: 'Old template version not allowed'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -167,20 +167,20 @@ async function templateCheck (measurement, param, res, next) {
|
|||||||
res.status(400).json({status: 'At least one value is required'});
|
res.status(400).json({status: 'At least one value is required'});
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const fillValues = {}; // initialize not given values with null
|
const fillValues = {}; // Initialize not given values with null
|
||||||
templateData.parameters.forEach(parameter => {
|
templateData.parameters.forEach(parameter => {
|
||||||
fillValues[parameter.name] = null;
|
fillValues[parameter.name] = null;
|
||||||
});
|
});
|
||||||
measurement.values = _.assign({}, fillValues, measurement.values);
|
measurement.values = _.assign({}, fillValues, measurement.values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate values
|
// Validate values
|
||||||
const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null');
|
const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null');
|
||||||
if (error) {res400(error, res); return false;}
|
if (error) {res400(error, res); return false;}
|
||||||
return value || true;
|
return value || true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStatus (status, req, res, next) { // set measurement status
|
function setStatus (status, req, res, next) { // Set measurement status
|
||||||
MeasurementModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
|
MeasurementModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ router.get('/model/groups', (req, res, next) => {
|
|||||||
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
let conditions: any = [{}, {}];
|
let conditions: any = [{}, {}];
|
||||||
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // if not dev or admin, user has to possess model rights
|
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // If not dev or admin, user has to possess model rights
|
||||||
conditions = [
|
conditions = [
|
||||||
{'models._id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}},
|
{'models._id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}},
|
||||||
{group: true, 'models.$': true}
|
{group: true, 'models.$': true}
|
||||||
@ -25,7 +25,7 @@ router.get('/model/groups', (req, res, next) => {
|
|||||||
ModelModel.find(...conditions).lean().exec((err, data) => {
|
ModelModel.find(...conditions).lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => ModelValidate.output(e))));
|
res.json(_.compact(data.map(e => ModelValidate.output(e))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -39,8 +39,8 @@ router.post('/model/:group', (req, res, next) => {
|
|||||||
ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
|
ModelModel.findOne({group: req.params.group}).lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
if (data) { // group exists
|
if (data) { // Group exists
|
||||||
if (data.models.find(e => e.name === model.name)) { // name exists, overwrite
|
if (data.models.find(e => e.name === model.name)) { // Name exists, overwrite
|
||||||
ModelModel.findOneAndUpdate(
|
ModelModel.findOneAndUpdate(
|
||||||
{$and: [{group: req.params.group}, {'models.name': model.name}]},
|
{$and: [{group: req.params.group}, {'models.name': model.name}]},
|
||||||
{'models.$': model},
|
{'models.$': model},
|
||||||
@ -49,7 +49,7 @@ router.post('/model/:group', (req, res, next) => {
|
|||||||
res.json({status: 'OK'})
|
res.json({status: 'OK'})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else { // create new
|
else { // Create new
|
||||||
ModelModel.findOneAndUpdate(
|
ModelModel.findOneAndUpdate(
|
||||||
{group: req.params.group},
|
{group: req.params.group},
|
||||||
{$push: {models: model as never}}
|
{$push: {models: model as never}}
|
||||||
@ -59,7 +59,7 @@ router.post('/model/:group', (req, res, next) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // create new group
|
else { // Create new group
|
||||||
new ModelModel({group: req.params.group, models: [model]}).save((err, data) => {
|
new ModelModel({group: req.params.group, models: [model]}).save((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
db.log(req, 'models', {_id: data._id}, data.toObject());
|
db.log(req, 'models', {_id: data._id}, data.toObject());
|
||||||
@ -78,11 +78,11 @@ router.delete('/model/:group(((?!file)[^\\/]+?))/:name', (req, res, next) => {
|
|||||||
if (!data || !data.models.find(e => e.name === req.params.name)) {
|
if (!data || !data.models.find(e => e.name === req.params.name)) {
|
||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
// delete all references in user.models
|
// Delete all references in user.models
|
||||||
UserModel.updateMany({}, {$pull: {models: data.models.find(e => e.name === req.params.name)._id as never}},
|
UserModel.updateMany({}, {$pull: {models: data.models.find(e => e.name === req.params.name)._id as never}},
|
||||||
{ multi: true }).log(req).lean().exec(err => {
|
{ multi: true }).log(req).lean().exec(err => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data.models.length > 1) { // only remove model
|
if (data.models.length > 1) { // Only remove model
|
||||||
ModelModel.findOneAndUpdate(
|
ModelModel.findOneAndUpdate(
|
||||||
{group: req.params.group},
|
{group: req.params.group},
|
||||||
{$pull: {models: data.models.find(e => e.name === req.params.name) as never}}
|
{$pull: {models: data.models.find(e => e.name === req.params.name) as never}}
|
||||||
@ -91,7 +91,7 @@ router.delete('/model/:group(((?!file)[^\\/]+?))/:name', (req, res, next) => {
|
|||||||
res.json({status: 'OK'})
|
res.json({status: 'OK'})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else { // remove document
|
else { // Remove document
|
||||||
ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => {
|
ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
res.json({status: 'OK'})
|
res.json({status: 'OK'})
|
||||||
@ -152,7 +152,7 @@ router.delete('/model/file/:name', (req, res, next) => {
|
|||||||
router.get('/model/authorized/:url', (req, res, next) => {
|
router.get('/model/authorized/:url', (req, res, next) => {
|
||||||
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // if not dev or admin, user has to possess model rights
|
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // If not dev or admin, user has to possess model rights
|
||||||
ModelModel.findOne({models: { $elemMatch: {
|
ModelModel.findOne({models: { $elemMatch: {
|
||||||
url: decodeURIComponent(req.params.url),
|
url: decodeURIComponent(req.params.url),
|
||||||
'_id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}
|
'_id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}
|
||||||
|
@ -208,7 +208,7 @@ describe('/', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// describe('A not connected database', () => { // RUN AS LAST OR RECONNECT DATABASE!!
|
// Describe('A not connected database', () => { // RUN AS LAST OR RECONNECT DATABASE!!
|
||||||
// it('resolves to an 500 error', done => {
|
// it('resolves to an 500 error', done => {
|
||||||
// db.disconnect(() => {
|
// db.disconnect(() => {
|
||||||
// TestHelper.request(server, done, {
|
// TestHelper.request(server, done, {
|
||||||
@ -223,7 +223,7 @@ describe('/', () => {
|
|||||||
|
|
||||||
describe('The /api/{url} redirect', () => {
|
describe('The /api/{url} redirect', () => {
|
||||||
let server;
|
let server;
|
||||||
let counter = 0; // count number of current test method
|
let counter = 0; // Count number of current test method
|
||||||
before(done => {
|
before(done => {
|
||||||
process.env.port = '2999';
|
process.env.port = '2999';
|
||||||
db.connect('test', done);
|
db.connect('test', done);
|
||||||
@ -246,7 +246,7 @@ describe('The /api/{url} redirect', () => {
|
|||||||
res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
|
res: {status: 'Authorization successful', method: 'basic', level: 'admin', user_id: '000000000000000000000003'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// it('is disabled in production', done => {
|
// It('is disabled in production', done => {
|
||||||
// TestHelper.request(server, done, {
|
// TestHelper.request(server, done, {
|
||||||
// method: 'get',
|
// method: 'get',
|
||||||
// url: '/api/authorized',
|
// url: '/api/authorized',
|
||||||
|
@ -37,7 +37,7 @@ router.get('/changelog/:id/:page?/:pagesize?', (req, res, next) => {
|
|||||||
.lean().exec((err, data) => {
|
.lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => RootValidate.changelogOutput(e))));
|
res.json(_.compact(data.map(e => RootValidate.changelogOutput(e))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1460,7 +1460,7 @@ describe('/sample', () => {
|
|||||||
}).end((err, res) => {
|
}).end((err, res) => {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
should(res.body).be.eql({status: 'OK'});
|
should(res.body).be.eql({status: 'OK'});
|
||||||
setTimeout(() => { // background action takes some time before we can check
|
setTimeout(() => { // Background action takes some time before we can check
|
||||||
NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
|
NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
should(data).have.property('sample_references').with.lengthOf(1);
|
should(data).have.property('sample_references').with.lengthOf(1);
|
||||||
|
@ -29,22 +29,22 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
const {error, value: filters} = SampleValidate.query(req.query, ['dev', 'admin'].indexOf(req.authDetails.level) >= 0);
|
const {error, value: filters} = SampleValidate.query(req.query, ['dev', 'admin'].indexOf(req.authDetails.level) >= 0);
|
||||||
if (error) return res400(error, res);
|
if (error) return res400(error, res);
|
||||||
|
|
||||||
// spectral data and csv not allowed for read/write users
|
// Spectral data and csv not allowed for read/write users
|
||||||
if ((filters.fields.find(e => e.indexOf('.' + globals.spectrum.dpt) >= 0) || filters.output !== 'json') &&
|
if ((filters.fields.find(e => e.indexOf('.' + globals.spectrum.dpt) >= 0) || filters.output !== 'json') &&
|
||||||
!req.auth(res, ['dev', 'admin'], 'all')) return;
|
!req.auth(res, ['dev', 'admin'], 'all')) return;
|
||||||
|
|
||||||
// evaluate sort parameter from 'color-asc' to ['color', 1]
|
// Evaluate sort parameter from 'color-asc' to ['color', 1]
|
||||||
filters.sort = filters.sort.split('-');
|
filters.sort = filters.sort.split('-');
|
||||||
filters.sort[0] = filters.sort[0] === 'added' ? '_id' : filters.sort[0]; // route added sorting criteria to _id
|
filters.sort[0] = filters.sort[0] === 'added' ? '_id' : filters.sort[0]; // Route added sorting criteria to _id
|
||||||
filters.sort[1] = filters.sort[1] === 'desc' ? -1 : 1;
|
filters.sort[1] = filters.sort[1] === 'desc' ? -1 : 1;
|
||||||
if (!filters['to-page']) { // set to-page default
|
if (!filters['to-page']) { // Set to-page default
|
||||||
filters['to-page'] = 0;
|
filters['to-page'] = 0;
|
||||||
}
|
}
|
||||||
const addedFilter = filters.filters.find(e => e.field === 'added');
|
const addedFilter = filters.filters.find(e => e.field === 'added');
|
||||||
if (addedFilter) { // convert added filter to object id
|
if (addedFilter) { // Convert added filter to object id
|
||||||
filters.filters.splice(filters.filters.findIndex(e => e.field === 'added'), 1);
|
filters.filters.splice(filters.filters.findIndex(e => e.field === 'added'), 1);
|
||||||
if (addedFilter.mode === 'in') {
|
if (addedFilter.mode === 'in') {
|
||||||
const v = []; // query value
|
const v = []; // Query value
|
||||||
addedFilter.values.forEach(value => {
|
addedFilter.values.forEach(value => {
|
||||||
const date = [new Date(value).setHours(0,0,0,0), new Date(value).setHours(23,59,59,999)];
|
const date = [new Date(value).setHours(0,0,0,0), new Date(value).setHours(23,59,59,999)];
|
||||||
v.push({$and: [{ _id: { '$gte': dateToOId(date[0])}}, { _id: { '$lte': dateToOId(date[1])}}]});
|
v.push({$and: [{ _id: { '$gte': dateToOId(date[0])}}, { _id: { '$lte': dateToOId(date[1])}}]});
|
||||||
@ -53,7 +53,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
else if (addedFilter.mode === 'nin') {
|
else if (addedFilter.mode === 'nin') {
|
||||||
addedFilter.values = addedFilter.values.sort();
|
addedFilter.values = addedFilter.values.sort();
|
||||||
const v = []; // query value
|
const v = []; // Query value
|
||||||
|
|
||||||
for (let i = 0; i <= addedFilter.values.length; i ++) {
|
for (let i = 0; i <= addedFilter.values.length; i ++) {
|
||||||
v[i] = {$and: []};
|
v[i] = {$and: []};
|
||||||
@ -69,19 +69,19 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
filters.filters.push({mode: 'or', field: '_id', values: v});
|
filters.filters.push({mode: 'or', field: '_id', values: v});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// start and end of day
|
// Start and end of day
|
||||||
const date = [new Date(addedFilter.values[0]).setHours(0,0,0,0),
|
const date = [new Date(addedFilter.values[0]).setHours(0,0,0,0),
|
||||||
new Date(addedFilter.values[0]).setHours(23,59,59,999)];
|
new Date(addedFilter.values[0]).setHours(23,59,59,999)];
|
||||||
if (addedFilter.mode === 'lt') { // lt start
|
if (addedFilter.mode === 'lt') { // Lt start
|
||||||
filters.filters.push({mode: 'lt', field: '_id', values: [dateToOId(date[0])]});
|
filters.filters.push({mode: 'lt', field: '_id', values: [dateToOId(date[0])]});
|
||||||
}
|
}
|
||||||
if (addedFilter.mode === 'eq' || addedFilter.mode === 'lte') { // lte end
|
if (addedFilter.mode === 'eq' || addedFilter.mode === 'lte') { // Lte end
|
||||||
filters.filters.push({mode: 'lte', field: '_id', values: [dateToOId(date[1])]});
|
filters.filters.push({mode: 'lte', field: '_id', values: [dateToOId(date[1])]});
|
||||||
}
|
}
|
||||||
if (addedFilter.mode === 'gt') { // gt end
|
if (addedFilter.mode === 'gt') { // Gt end
|
||||||
filters.filters.push({mode: 'gt', field: '_id', values: [dateToOId(date[1])]});
|
filters.filters.push({mode: 'gt', field: '_id', values: [dateToOId(date[1])]});
|
||||||
}
|
}
|
||||||
if (addedFilter.mode === 'eq' || addedFilter.mode === 'gte') { // gte start
|
if (addedFilter.mode === 'eq' || addedFilter.mode === 'gte') { // Gte start
|
||||||
filters.filters.push({mode: 'gte', field: '_id', values: [dateToOId(date[0])]});
|
filters.filters.push({mode: 'gte', field: '_id', values: [dateToOId(date[0])]});
|
||||||
}
|
}
|
||||||
if (addedFilter.mode === 'ne') {
|
if (addedFilter.mode === 'ne') {
|
||||||
@ -98,7 +98,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
let queryPtr = query;
|
let queryPtr = query;
|
||||||
queryPtr.push({$match: {$and: []}});
|
queryPtr.push({$match: {$and: []}});
|
||||||
|
|
||||||
if (filters.sort[0].indexOf('measurements.') >= 0) { // sorting with measurements as starting collection
|
if (filters.sort[0].indexOf('measurements.') >= 0) { // Sorting with measurements as starting collection
|
||||||
collection = MeasurementModel;
|
collection = MeasurementModel;
|
||||||
const [,measurementName, measurementParam] = filters.sort[0].split('.');
|
const [,measurementName, measurementParam] = filters.sort[0].split('.');
|
||||||
const measurementTemplates = await MeasurementTemplateModel.find({name: measurementName})
|
const measurementTemplates = await MeasurementTemplateModel.find({name: measurementName})
|
||||||
@ -108,7 +108,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
return res.status(400).json({status: 'Invalid body format', details: filters.sort[0] + ' not found'});
|
return res.status(400).json({status: 'Invalid body format', details: filters.sort[0] + ' not found'});
|
||||||
}
|
}
|
||||||
let sortStartValue = null;
|
let sortStartValue = null;
|
||||||
if (filters['from-id']) { // from-id specified, fetch values for sorting
|
if (filters['from-id']) { // From-id specified, fetch values for sorting
|
||||||
const fromSample = await MeasurementModel.findOne({sample_id: mongoose.Types.ObjectId(filters['from-id'])})
|
const fromSample = await MeasurementModel.findOne({sample_id: mongoose.Types.ObjectId(filters['from-id'])})
|
||||||
.lean().exec().catch(err => {next(err);});
|
.lean().exec().catch(err => {next(err);});
|
||||||
if (fromSample instanceof Error) return;
|
if (fromSample instanceof Error) return;
|
||||||
@ -117,29 +117,29 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
sortStartValue = fromSample.values[measurementParam];
|
sortStartValue = fromSample.values[measurementParam];
|
||||||
}
|
}
|
||||||
// find measurements to sort
|
// Find measurements to sort
|
||||||
queryPtr[0].$match.$and.push({measurement_template: {$in: measurementTemplates.map(e => e._id)}});
|
queryPtr[0].$match.$and.push({measurement_template: {$in: measurementTemplates.map(e => e._id)}});
|
||||||
if (filters.filters.find(e => e.field === filters.sort[0])) { // sorted measurement should also be filtered
|
if (filters.filters.find(e => e.field === filters.sort[0])) { // Sorted measurement should also be filtered
|
||||||
queryPtr[0].$match.$and.push(...filterQueries(filters.filters.filter(e => e.field === filters.sort[0])
|
queryPtr[0].$match.$and.push(...filterQueries(filters.filters.filter(e => e.field === filters.sort[0])
|
||||||
.map(e => {e.field = 'values.' + e.field.split('.')[2]; return e; })));
|
.map(e => {e.field = 'values.' + e.field.split('.')[2]; return e; })));
|
||||||
}
|
}
|
||||||
queryPtr.push(
|
queryPtr.push(
|
||||||
...sortQuery(filters, ['values.' + measurementParam, 'sample_id'], sortStartValue), // sort measurements
|
...sortQuery(filters, ['values.' + measurementParam, 'sample_id'], sortStartValue), // Sort measurements
|
||||||
{$replaceRoot: {newRoot: {measurement: '$$ROOT'}}}, // fetch samples and restructure them to fit sample structure
|
{$replaceRoot: {newRoot: {measurement: '$$ROOT'}}}, // Fetch samples and restructure them to fit sample structure
|
||||||
{$lookup: {from: 'samples', localField: 'measurement.sample_id', foreignField: '_id', as: 'sample'}},
|
{$lookup: {from: 'samples', localField: 'measurement.sample_id', foreignField: '_id', as: 'sample'}},
|
||||||
{$match: statusQuery(filters, 'sample.status')}, // filter out wrong status once samples were added
|
{$match: statusQuery(filters, 'sample.status')}, // Filter out wrong status once samples were added
|
||||||
{$addFields: {['sample.' + measurementName]: '$measurement.values'}}, // more restructuring
|
{$addFields: {['sample.' + measurementName]: '$measurement.values'}}, // More restructuring
|
||||||
{$replaceRoot: {newRoot: {$mergeObjects: [{$arrayElemAt: ['$sample', 0]}, {}]}}}
|
{$replaceRoot: {newRoot: {$mergeObjects: [{$arrayElemAt: ['$sample', 0]}, {}]}}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else { // sorting with samples as starting collection
|
else { // Sorting with samples as starting collection
|
||||||
collection = SampleModel;
|
collection = SampleModel;
|
||||||
queryPtr[0].$match.$and.push(statusQuery(filters, 'status'));
|
queryPtr[0].$match.$and.push(statusQuery(filters, 'status'));
|
||||||
|
|
||||||
// sorting for sample keys
|
// Sorting for sample keys
|
||||||
if (SampleValidate.sampleKeys.indexOf(filters.sort[0]) >= 0 || /condition\./.test(filters.sort[0])) {
|
if (SampleValidate.sampleKeys.indexOf(filters.sort[0]) >= 0 || /condition\./.test(filters.sort[0])) {
|
||||||
let sortStartValue = null;
|
let sortStartValue = null;
|
||||||
if (filters['from-id']) { // from-id specified
|
if (filters['from-id']) { // From-id specified
|
||||||
const fromSample = await SampleModel.findById(filters['from-id']).lean().exec().catch(err => {
|
const fromSample = await SampleModel.findById(filters['from-id']).lean().exec().catch(err => {
|
||||||
next(err);
|
next(err);
|
||||||
});
|
});
|
||||||
@ -151,28 +151,28 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
queryPtr.push(...sortQuery(filters, [filters.sort[0], '_id'], sortStartValue));
|
queryPtr.push(...sortQuery(filters, [filters.sort[0], '_id'], sortStartValue));
|
||||||
}
|
}
|
||||||
else { // add sort key to list to add field later
|
else { // Add sort key to list to add field later
|
||||||
sortFilterKeys.push(filters.sort[0]);
|
sortFilterKeys.push(filters.sort[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addFilterQueries(queryPtr, filters.filters.filter(
|
addFilterQueries(queryPtr, filters.filters.filter(
|
||||||
e => (SampleValidate.sampleKeys.indexOf(e.field) >= 0) || /condition\./.test(e.field))
|
e => (SampleValidate.sampleKeys.indexOf(e.field) >= 0) || /condition\./.test(e.field))
|
||||||
); // sample filters
|
); // Sample filters
|
||||||
|
|
||||||
let materialQuery = []; // put material query together separate first to reuse for first-id
|
let materialQuery = []; // Put material query together separate first to reuse for first-id
|
||||||
let materialAdded = false;
|
let materialAdded = false;
|
||||||
if (sortFilterKeys.find(e => /material\./.test(e))) { // add material fields
|
if (sortFilterKeys.find(e => /material\./.test(e))) { // add material fields
|
||||||
materialAdded = true;
|
materialAdded = true;
|
||||||
materialQuery.push( // add material properties
|
materialQuery.push( // Add material properties
|
||||||
{$lookup: {from: 'materials', localField: 'material_id', foreignField: '_id', as: 'material'}},
|
{$lookup: {from: 'materials', localField: 'material_id', foreignField: '_id', as: 'material'}},
|
||||||
{$addFields: {material: {$arrayElemAt: ['$material', 0]}}}
|
{$addFields: {material: {$arrayElemAt: ['$material', 0]}}}
|
||||||
);
|
);
|
||||||
const baseMFilters = sortFilterKeys.filter(e => /material\./.test(e))
|
const baseMFilters = sortFilterKeys.filter(e => /material\./.test(e))
|
||||||
.filter(e => ['material.supplier', 'material.group', 'material.number'].indexOf(e) < 0);
|
.filter(e => ['material.supplier', 'material.group', 'material.number'].indexOf(e) < 0);
|
||||||
// base material filters
|
// Base material filters
|
||||||
addFilterQueries(materialQuery, filters.filters.filter(e => baseMFilters.indexOf(e.field) >= 0));
|
addFilterQueries(materialQuery, filters.filters.filter(e => baseMFilters.indexOf(e.field) >= 0));
|
||||||
if (sortFilterKeys.find(e => e === 'material.supplier')) { // add supplier if needed
|
if (sortFilterKeys.find(e => e === 'material.supplier')) { // Add supplier if needed
|
||||||
materialQuery.push(
|
materialQuery.push(
|
||||||
{$lookup: {
|
{$lookup: {
|
||||||
from: 'material_suppliers', localField: 'material.supplier_id', foreignField: '_id', as: 'material.supplier'}
|
from: 'material_suppliers', localField: 'material.supplier_id', foreignField: '_id', as: 'material.supplier'}
|
||||||
@ -180,7 +180,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
{$addFields: {'material.supplier': {$arrayElemAt: ['$material.supplier.name', 0]}}}
|
{$addFields: {'material.supplier': {$arrayElemAt: ['$material.supplier.name', 0]}}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (sortFilterKeys.find(e => e === 'material.group')) { // add group if needed
|
if (sortFilterKeys.find(e => e === 'material.group')) { // Add group if needed
|
||||||
materialQuery.push(
|
materialQuery.push(
|
||||||
{$lookup: {
|
{$lookup: {
|
||||||
from: 'material_groups', localField: 'material.group_id', foreignField: '_id', as: 'material.group' }
|
from: 'material_groups', localField: 'material.group_id', foreignField: '_id', as: 'material.group' }
|
||||||
@ -190,12 +190,12 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
const specialMFilters = sortFilterKeys.filter(e => /material\./.test(e))
|
const specialMFilters = sortFilterKeys.filter(e => /material\./.test(e))
|
||||||
.filter(e => ['material.supplier', 'material.group', 'material.number'].indexOf(e) >= 0);
|
.filter(e => ['material.supplier', 'material.group', 'material.number'].indexOf(e) >= 0);
|
||||||
// base material filters
|
// Base material filters
|
||||||
addFilterQueries(materialQuery, filters.filters.filter(e => specialMFilters.indexOf(e.field) >= 0));
|
addFilterQueries(materialQuery, filters.filters.filter(e => specialMFilters.indexOf(e.field) >= 0));
|
||||||
queryPtr.push(...materialQuery);
|
queryPtr.push(...materialQuery);
|
||||||
if (/material\./.test(filters.sort[0])) { // sort by material key
|
if (/material\./.test(filters.sort[0])) { // Sort by material key
|
||||||
let sortStartValue = null;
|
let sortStartValue = null;
|
||||||
if (filters['from-id']) { // from-id specified
|
if (filters['from-id']) { // From-id specified
|
||||||
const fromSample = await SampleModel.aggregate(
|
const fromSample = await SampleModel.aggregate(
|
||||||
[{$match: {_id: mongoose.Types.ObjectId(filters['from-id'])}}, ...materialQuery]
|
[{$match: {_id: mongoose.Types.ObjectId(filters['from-id'])}}, ...materialQuery]
|
||||||
).exec().catch(err => {next(err);});
|
).exec().catch(err => {next(err);});
|
||||||
@ -215,7 +215,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortFilterKeys.find(e => e === 'measurements')) { // filter for samples without measurements
|
if (sortFilterKeys.find(e => e === 'measurements')) { // Filter for samples without measurements
|
||||||
queryPtr.push({$lookup: {
|
queryPtr.push({$lookup: {
|
||||||
from: 'measurements', let: {sId: '$_id'},
|
from: 'measurements', let: {sId: '$_id'},
|
||||||
pipeline: [{$match:{$expr:{$and:[{$eq:['$sample_id','$$sId']}]}}}, {$project: {_id: true}}],
|
pipeline: [{$match:{$expr:{$and:[{$eq:['$sample_id','$$sId']}]}}}, {$project: {_id: true}}],
|
||||||
@ -225,7 +225,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const measurementFilterFields = _.uniq(sortFilterKeys.filter(e => /measurements\./.test(e))
|
const measurementFilterFields = _.uniq(sortFilterKeys.filter(e => /measurements\./.test(e))
|
||||||
.map(e => e.split('.')[1])); // filter measurement names and remove duplicates from parameters
|
.map(e => e.split('.')[1])); // Filter measurement names and remove duplicates from parameters
|
||||||
if (sortFilterKeys.find(e => /measurements\./.test(e))) { // add measurement fields
|
if (sortFilterKeys.find(e => /measurements\./.test(e))) { // add measurement fields
|
||||||
const measurementTemplates = await MeasurementTemplateModel.find({name: {$in: measurementFilterFields}})
|
const measurementTemplates = await MeasurementTemplateModel.find({name: {$in: measurementFilterFields}})
|
||||||
.lean().exec().catch(err => {next(err);});
|
.lean().exec().catch(err => {next(err);});
|
||||||
@ -238,7 +238,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
{$in: ['$measurement_template', measurementTemplates.map(e => mongoose.Types.ObjectId(e._id))]}
|
{$in: ['$measurement_template', measurementTemplates.map(e => mongoose.Types.ObjectId(e._id))]}
|
||||||
]}}}
|
]}}}
|
||||||
];
|
];
|
||||||
if (measurementFilterFields.indexOf(globals.spectrum.spectrum) >= 0) { // filter out dpts
|
if (measurementFilterFields.indexOf(globals.spectrum.spectrum) >= 0) { // Filter out dpts
|
||||||
pipeline.push(
|
pipeline.push(
|
||||||
{$project: {['values.' + globals.spectrum.dpt]: false}},
|
{$project: {['values.' + globals.spectrum.dpt]: false}},
|
||||||
{$addFields: {'values._id': '$_id'}}
|
{$addFields: {'values._id': '$_id'}}
|
||||||
@ -264,7 +264,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
addFilterQueries(queryPtr, filters.filters
|
addFilterQueries(queryPtr, filters.filters
|
||||||
.filter(e => sortFilterKeys.filter(e => /measurements\./.test(e)).indexOf(e.field) >= 0)
|
.filter(e => sortFilterKeys.filter(e => /measurements\./.test(e)).indexOf(e.field) >= 0)
|
||||||
.map(e => {e.field = e.field.replace('measurements.', ''); return e; })
|
.map(e => {e.field = e.field.replace('measurements.', ''); return e; })
|
||||||
); // measurement filters
|
); // Measurement filters
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortFilterKeys.find(e => e === 'notes.comment')) {
|
if (sortFilterKeys.find(e => e === 'notes.comment')) {
|
||||||
@ -272,18 +272,18 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
addFilterQueries(queryPtr, filters.filters.filter(e => e.field === 'notes.comment'));
|
addFilterQueries(queryPtr, filters.filters.filter(e => e.field === 'notes.comment'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// count total number of items before $skip and $limit, only works when from-id is not specified and spectra are not
|
// Count total number of items before $skip and $limit, only works when from-id is not specified and spectra are not
|
||||||
// included
|
// Included
|
||||||
if (!filters.fields.find(e =>
|
if (!filters.fields.find(e =>
|
||||||
e.indexOf(globals.spectrum.spectrum + '.' + globals.spectrum.dpt) >= 0) && !filters['from-id']
|
e.indexOf(globals.spectrum.spectrum + '.' + globals.spectrum.dpt) >= 0) && !filters['from-id']
|
||||||
) {
|
) {
|
||||||
queryPtr.push({$facet: {count: [{$count: 'count'}], samples: []}});
|
queryPtr.push({$facet: {count: [{$count: 'count'}], samples: []}});
|
||||||
queryPtr = queryPtr[queryPtr.length - 1].$facet.samples; // add rest of aggregation pipeline into $facet
|
queryPtr = queryPtr[queryPtr.length - 1].$facet.samples; // Add rest of aggregation pipeline into $facet
|
||||||
}
|
}
|
||||||
|
|
||||||
// paging
|
// Paging
|
||||||
if (filters['to-page']) {
|
if (filters['to-page']) {
|
||||||
// number to skip, if going back pages, one page has to be skipped less but on sample more
|
// Number to skip, if going back pages, one page has to be skipped less but on sample more
|
||||||
queryPtr.push({$skip: Math.abs(filters['to-page'] + Number(filters['to-page'] < 0)) * filters['page-size'] +
|
queryPtr.push({$skip: Math.abs(filters['to-page'] + Number(filters['to-page'] < 0)) * filters['page-size'] +
|
||||||
Number(filters['to-page'] < 0)})
|
Number(filters['to-page'] < 0)})
|
||||||
}
|
}
|
||||||
@ -291,22 +291,22 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
queryPtr.push({$limit: filters['page-size']});
|
queryPtr.push({$limit: filters['page-size']});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldsToAdd = filters.fields.filter(e => // fields to add
|
const fieldsToAdd = filters.fields.filter(e => // Fields to add
|
||||||
sortFilterKeys.indexOf(e) < 0 // field was not in filter
|
sortFilterKeys.indexOf(e) < 0 // Field was not in filter
|
||||||
&& e !== filters.sort[0] // field was not in sort
|
&& e !== filters.sort[0] // Field was not in sort
|
||||||
);
|
);
|
||||||
|
|
||||||
if (fieldsToAdd.find(e => /^notes(\..+|$)/m.test(e))) { // add notes
|
if (fieldsToAdd.find(e => /^notes(\..+|$)/m.test(e))) { // Add notes
|
||||||
addNotes(queryPtr);
|
addNotes(queryPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldsToAdd.find(e => /material\./.test(e)) && !materialAdded) { // add material, was not added already
|
if (fieldsToAdd.find(e => /material\./.test(e)) && !materialAdded) { // Add material, was not added already
|
||||||
queryPtr.push(
|
queryPtr.push(
|
||||||
{$lookup: {from: 'materials', localField: 'material_id', foreignField: '_id', as: 'material'}},
|
{$lookup: {from: 'materials', localField: 'material_id', foreignField: '_id', as: 'material'}},
|
||||||
{$addFields: {material: { $arrayElemAt: ['$material', 0]}}}
|
{$addFields: {material: { $arrayElemAt: ['$material', 0]}}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (fieldsToAdd.indexOf('material.supplier') >= 0) { // add supplier if needed
|
if (fieldsToAdd.indexOf('material.supplier') >= 0) { // Add supplier if needed
|
||||||
queryPtr.push(
|
queryPtr.push(
|
||||||
{$lookup: {
|
{$lookup: {
|
||||||
from: 'material_suppliers', localField: 'material.supplier_id', foreignField: '_id', as: 'material.supplier'
|
from: 'material_suppliers', localField: 'material.supplier_id', foreignField: '_id', as: 'material.supplier'
|
||||||
@ -314,7 +314,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
{$addFields: {'material.supplier': {$arrayElemAt: ['$material.supplier.name', 0]}}}
|
{$addFields: {'material.supplier': {$arrayElemAt: ['$material.supplier.name', 0]}}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (fieldsToAdd.indexOf('material.group') >= 0) { // add group if needed
|
if (fieldsToAdd.indexOf('material.group') >= 0) { // Add group if needed
|
||||||
queryPtr.push(
|
queryPtr.push(
|
||||||
{$lookup: {
|
{$lookup: {
|
||||||
from: 'material_groups', localField: 'material.group_id', foreignField: '_id', as: 'material.group'
|
from: 'material_groups', localField: 'material.group_id', foreignField: '_id', as: 'material.group'
|
||||||
@ -325,17 +325,17 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
|
|
||||||
let measurementFieldsFields: string[] = _.uniq(
|
let measurementFieldsFields: string[] = _.uniq(
|
||||||
fieldsToAdd.filter(e => /measurements\./.test(e)).map(e => e.split('.')[1])
|
fieldsToAdd.filter(e => /measurements\./.test(e)).map(e => e.split('.')[1])
|
||||||
); // filter measurement names and remove duplicates from parameters
|
); // Filter measurement names and remove duplicates from parameters
|
||||||
if (fieldsToAdd.find(e => /measurements\./.test(e))) { // add measurement fields
|
if (fieldsToAdd.find(e => /measurements\./.test(e))) { // Add measurement fields
|
||||||
const measurementTemplates = await MeasurementTemplateModel.find({name: {$in: measurementFieldsFields}})
|
const measurementTemplates = await MeasurementTemplateModel.find({name: {$in: measurementFieldsFields}})
|
||||||
.lean().exec().catch(err => {next(err);});
|
.lean().exec().catch(err => {next(err);});
|
||||||
if (measurementTemplates instanceof Error) return;
|
if (measurementTemplates instanceof Error) return;
|
||||||
if (measurementTemplates.length < measurementFieldsFields.length) {
|
if (measurementTemplates.length < measurementFieldsFields.length) {
|
||||||
return res.status(400).json({status: 'Invalid body format', details: 'Measurement key not found'});
|
return res.status(400).json({status: 'Invalid body format', details: 'Measurement key not found'});
|
||||||
}
|
}
|
||||||
// use different lookup methods with and without dpt for the best performance
|
// Use different lookup methods with and without dpt for the best performance
|
||||||
if (fieldsToAdd.find(e => new RegExp('measurements\\.' + globals.spectrum.spectrum).test(e))) { // with dpt
|
if (fieldsToAdd.find(e => new RegExp('measurements\\.' + globals.spectrum.spectrum).test(e))) { // With dpt
|
||||||
// spectrum was already used for filters
|
// Spectrum was already used for filters
|
||||||
if (sortFilterKeys.find(e => new RegExp('measurements\\.' + globals.spectrum.spectrum).test(e))) {
|
if (sortFilterKeys.find(e => new RegExp('measurements\\.' + globals.spectrum.spectrum).test(e))) {
|
||||||
queryPtr.push(
|
queryPtr.push(
|
||||||
{$lookup: {from: 'measurements', localField: 'spectrum._id', foreignField: '_id', as: 'measurements'}}
|
{$lookup: {from: 'measurements', localField: 'spectrum._id', foreignField: '_id', as: 'measurements'}}
|
||||||
@ -380,11 +380,11 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
|
|
||||||
const projection = filters.fields.map(e => e.replace('measurements.', ''))
|
const projection = filters.fields.map(e => e.replace('measurements.', ''))
|
||||||
.reduce((s, e) => {s[e] = true; return s; }, {});
|
.reduce((s, e) => {s[e] = true; return s; }, {});
|
||||||
if (filters.fields.indexOf('_id') < 0 && filters.fields.indexOf('added') < 0) { // disable _id explicitly
|
if (filters.fields.indexOf('_id') < 0 && filters.fields.indexOf('added') < 0) { // Disable _id explicitly
|
||||||
projection._id = false;
|
projection._id = false;
|
||||||
}
|
}
|
||||||
queryPtr.push({$project: projection});
|
queryPtr.push({$project: projection});
|
||||||
// use streaming when including spectrum files
|
// Use streaming when including spectrum files
|
||||||
if (!fieldsToAdd.find(e => e.indexOf(globals.spectrum.spectrum + '.' + globals.spectrum.dpt) >= 0)) {
|
if (!fieldsToAdd.find(e => e.indexOf(globals.spectrum.spectrum + '.' + globals.spectrum.dpt) >= 0)) {
|
||||||
collection.aggregate(query).allowDiskUse(true).exec((err, data) => {
|
collection.aggregate(query).allowDiskUse(true).exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
@ -393,7 +393,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
res.header('Access-Control-Expose-Headers', 'x-total-items');
|
res.header('Access-Control-Expose-Headers', 'x-total-items');
|
||||||
data = data[0].samples;
|
data = data[0].samples;
|
||||||
}
|
}
|
||||||
if (filters.fields.indexOf('added') >= 0) { // add added date
|
if (filters.fields.indexOf('added') >= 0) { // Add added date
|
||||||
data.map(e => {
|
data.map(e => {
|
||||||
e.added = e._id.getTimestamp();
|
e.added = e._id.getTimestamp();
|
||||||
if (filters.fields.indexOf('_id') < 0) {
|
if (filters.fields.indexOf('_id') < 0) {
|
||||||
@ -409,7 +409,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
[filters.sort[0].split('.')[1],
|
[filters.sort[0].split('.')[1],
|
||||||
...measurementFilterFields, ...measurementFieldsFields]
|
...measurementFilterFields, ...measurementFieldsFields]
|
||||||
);
|
);
|
||||||
if (filters.output === 'csv') { // output as csv
|
if (filters.output === 'csv') { // Output as csv
|
||||||
csv(_.compact(data.map(e => SampleValidate.output(e, 'refs', measurementFields))), (err, data) => {
|
csv(_.compact(data.map(e => SampleValidate.output(e, 'refs', measurementFields))), (err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
res.set('Content-Type', 'text/csv');
|
res.set('Content-Type', 'text/csv');
|
||||||
@ -420,7 +420,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
else if (filters.output === 'flatten') {
|
else if (filters.output === 'flatten') {
|
||||||
res.json(_.compact(data.map(e => flatten(SampleValidate.output(e, 'refs', measurementFields), true))));
|
res.json(_.compact(data.map(e => flatten(SampleValidate.output(e, 'refs', measurementFields), true))));
|
||||||
}
|
}
|
||||||
else { // validate all and filter null values from validation errors
|
else { // Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => SampleValidate.output(e, 'refs', measurementFields))));
|
res.json(_.compact(data.map(e => SampleValidate.output(e, 'refs', measurementFields))));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -431,7 +431,7 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
let count = 0;
|
let count = 0;
|
||||||
const stream = collection.aggregate(query).allowDiskUse(true).cursor().exec();
|
const stream = collection.aggregate(query).allowDiskUse(true).cursor().exec();
|
||||||
stream.on('data', data => {
|
stream.on('data', data => {
|
||||||
if (filters.fields.indexOf('added') >= 0) { // add added date
|
if (filters.fields.indexOf('added') >= 0) { // Add added date
|
||||||
data.added = data._id.getTimestamp();
|
data.added = data._id.getTimestamp();
|
||||||
if (filters.fields.indexOf('_id') < 0) {
|
if (filters.fields.indexOf('_id') < 0) {
|
||||||
delete data._id;
|
delete data._id;
|
||||||
@ -457,7 +457,7 @@ router.get(`/samples/:state(${globals.status.new}|${globals.status.del})`, (req,
|
|||||||
|
|
||||||
SampleModel.find({status: req.params.state}).lean().exec((err, data) => {
|
SampleModel.find({status: req.params.state}).lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => SampleValidate.output(e))));
|
res.json(_.compact(data.map(e => SampleValidate.output(e))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -487,7 +487,7 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
const {error, value: sample} = SampleValidate.input(req.body, 'change');
|
const {error, value: sample} = SampleValidate.input(req.body, 'change');
|
||||||
if (error) return res400(error, res);
|
if (error) return res400(error, res);
|
||||||
|
|
||||||
SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => { // check if id exists
|
SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => { // Check if id exists
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (!sampleData) {
|
if (!sampleData) {
|
||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
@ -496,12 +496,12 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
return res.status(403).json({status: 'Forbidden'});
|
return res.status(403).json({status: 'Forbidden'});
|
||||||
}
|
}
|
||||||
|
|
||||||
// only dev and admin are allowed to edit other user's data
|
// Only dev and admin are allowed to edit other user's data
|
||||||
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic')) return;
|
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic')) return;
|
||||||
if (sample.hasOwnProperty('material_id')) {
|
if (sample.hasOwnProperty('material_id')) {
|
||||||
if (!await materialCheck(sample, res, next)) return;
|
if (!await materialCheck(sample, res, next)) return;
|
||||||
}
|
}
|
||||||
// do not execute check if condition is and was empty
|
// Do not execute check if condition is and was empty
|
||||||
if (sample.hasOwnProperty('condition') && !(_.isEmpty(sample.condition) && _.isEmpty(sampleData.condition))) {
|
if (sample.hasOwnProperty('condition') && !(_.isEmpty(sample.condition) && _.isEmpty(sampleData.condition))) {
|
||||||
sample.condition = await conditionCheck(sample.condition, 'change', res, next,
|
sample.condition = await conditionCheck(sample.condition, 'change', res, next,
|
||||||
!(sampleData.condition.condition_template &&
|
!(sampleData.condition.condition_template &&
|
||||||
@ -511,35 +511,35 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
|
|
||||||
if (sample.hasOwnProperty('notes')) {
|
if (sample.hasOwnProperty('notes')) {
|
||||||
let newNotes = true;
|
let newNotes = true;
|
||||||
if (sampleData.note_id !== null) { // old notes data exists
|
if (sampleData.note_id !== null) { // Old notes data exists
|
||||||
const data = await NoteModel.findById(sampleData.note_id).lean().exec().catch(err => {next(err);}) as any;
|
const data = await NoteModel.findById(sampleData.note_id).lean().exec().catch(err => {next(err);}) as any;
|
||||||
if (data instanceof Error) return;
|
if (data instanceof Error) return;
|
||||||
// check if notes were changed
|
// Check if notes were changed
|
||||||
newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes);
|
newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes);
|
||||||
if (newNotes) {
|
if (newNotes) {
|
||||||
if (data.hasOwnProperty('custom_fields')) { // update note_fields
|
if (data.hasOwnProperty('custom_fields')) { // Update note_fields
|
||||||
customFieldsChange(Object.keys(data.custom_fields), -1, req);
|
customFieldsChange(Object.keys(data.custom_fields), -1, req);
|
||||||
}
|
}
|
||||||
await NoteModel.findByIdAndDelete(sampleData.note_id).log(req).lean().exec(err => { // delete old notes
|
await NoteModel.findByIdAndDelete(sampleData.note_id).log(req).lean().exec(err => { // Delete old notes
|
||||||
if (err) return console.error(err);
|
if (err) return console.error(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.keys(sample.notes).length > 0 && newNotes) { // save new notes
|
if (_.keys(sample.notes).length > 0 && newNotes) { // Save new notes
|
||||||
if (!await sampleRefCheck(sample, res, next)) return;
|
if (!await sampleRefCheck(sample, res, next)) return;
|
||||||
// new custom_fields
|
// New custom_fields
|
||||||
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {
|
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {
|
||||||
customFieldsChange(Object.keys(sample.notes.custom_fields), 1, req);
|
customFieldsChange(Object.keys(sample.notes.custom_fields), 1, req);
|
||||||
}
|
}
|
||||||
let data = await new NoteModel(sample.notes).save().catch(err => { return next(err)}); // save new notes
|
let data = await new NoteModel(sample.notes).save().catch(err => { return next(err)}); // Save new notes
|
||||||
db.log(req, 'notes', {_id: data._id}, data.toObject());
|
db.log(req, 'notes', {_id: data._id}, data.toObject());
|
||||||
delete sample.notes;
|
delete sample.notes;
|
||||||
sample.note_id = data._id;
|
sample.note_id = data._id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for changes
|
// Check for changes
|
||||||
if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) {
|
if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) {
|
||||||
sample.status = globals.status.new;
|
sample.status = globals.status.new;
|
||||||
}
|
}
|
||||||
@ -555,28 +555,28 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||||
if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => { // check if id exists
|
SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => { // Check if id exists
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (!sampleData) {
|
if (!sampleData) {
|
||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
|
|
||||||
// only dev and admin are allowed to edit other user's data
|
// Only dev and admin are allowed to edit other user's data
|
||||||
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic')) return;
|
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
// set sample status
|
// Set sample status
|
||||||
await SampleModel.findByIdAndUpdate(req.params.id, {status:'deleted'}).log(req).lean().exec(err => {
|
await SampleModel.findByIdAndUpdate(req.params.id, {status:'deleted'}).log(req).lean().exec(err => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// set status of associated measurements also to deleted
|
// Set status of associated measurements also to deleted
|
||||||
MeasurementModel.updateMany({sample_id: mongoose.Types.ObjectId(req.params.id)}, {status: globals.status.del})
|
MeasurementModel.updateMany({sample_id: mongoose.Types.ObjectId(req.params.id)}, {status: globals.status.del})
|
||||||
.log(req).lean().exec(err => {
|
.log(req).lean().exec(err => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
if (sampleData.note_id !== null) { // handle notes
|
if (sampleData.note_id !== null) { // Handle notes
|
||||||
NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields
|
NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // Find notes to update note_fields
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data.hasOwnProperty('custom_fields')) { // update note_fields
|
if (data.hasOwnProperty('custom_fields')) { // Update note_fields
|
||||||
customFieldsChange(Object.keys(data.custom_fields), -1, req);
|
customFieldsChange(Object.keys(data.custom_fields), -1, req);
|
||||||
}
|
}
|
||||||
res.json({status: 'OK'});
|
res.json({status: 'OK'});
|
||||||
@ -615,7 +615,7 @@ router.put('/sample/validate/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
router.post('/sample/new', async (req, res, next) => {
|
router.post('/sample/new', async (req, res, next) => {
|
||||||
if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['write', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
if (!req.body.hasOwnProperty('condition')) { // add empty condition if not specified
|
if (!req.body.hasOwnProperty('condition')) { // Add empty condition if not specified
|
||||||
req.body.condition = {};
|
req.body.condition = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,17 +626,17 @@ router.post('/sample/new', async (req, res, next) => {
|
|||||||
if (!await materialCheck(sample, res, next)) return;
|
if (!await materialCheck(sample, res, next)) return;
|
||||||
if (!await sampleRefCheck(sample, res, next)) return;
|
if (!await sampleRefCheck(sample, res, next)) return;
|
||||||
|
|
||||||
// new custom_fields
|
// New custom_fields
|
||||||
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {
|
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {
|
||||||
customFieldsChange(Object.keys(sample.notes.custom_fields), 1, req);
|
customFieldsChange(Object.keys(sample.notes.custom_fields), 1, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isEmpty(sample.condition)) { // do not execute check if condition is empty
|
if (!_.isEmpty(sample.condition)) { // Do not execute check if condition is empty
|
||||||
sample.condition = await conditionCheck(sample.condition, 'change', res, next);
|
sample.condition = await conditionCheck(sample.condition, 'change', res, next);
|
||||||
if (!sample.condition) return;
|
if (!sample.condition) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sample.status = globals.status.new; // set status to new
|
sample.status = globals.status.new; // Set status to new
|
||||||
if (sample.hasOwnProperty('number')) {
|
if (sample.hasOwnProperty('number')) {
|
||||||
if (!await numberCheck(sample, res, next)) return;
|
if (!await numberCheck(sample, res, next)) return;
|
||||||
}
|
}
|
||||||
@ -645,7 +645,7 @@ router.post('/sample/new', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
if (!sample.number) return;
|
if (!sample.number) return;
|
||||||
|
|
||||||
await new NoteModel(sample.notes).save((err, data) => { // save notes
|
await new NoteModel(sample.notes).save((err, data) => { // Save notes
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
db.log(req, 'notes', {_id: data._id}, data.toObject());
|
db.log(req, 'notes', {_id: data._id}, data.toObject());
|
||||||
delete sample.notes;
|
delete sample.notes;
|
||||||
@ -665,7 +665,7 @@ router.get('/sample/notes/fields', (req, res, next) => {
|
|||||||
|
|
||||||
NoteFieldModel.find({}).lean().exec((err, data) => {
|
NoteFieldModel.find({}).lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => NoteFieldValidate.output(e))));
|
res.json(_.compact(data.map(e => NoteFieldValidate.output(e))));
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -673,10 +673,10 @@ router.get('/sample/notes/fields', (req, res, next) => {
|
|||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
// store the highest generated number for each location to avoid duplicate numbers
|
// Store the highest generated number for each location to avoid duplicate numbers
|
||||||
const numberBuffer: {[location: string]: number} = {};
|
const numberBuffer: {[location: string]: number} = {};
|
||||||
|
|
||||||
// generate number in format Location32, returns false on error
|
// Generate number in format Location32, returns false on error
|
||||||
async function numberGenerate (sample, req, res, next) {
|
async function numberGenerate (sample, req, res, next) {
|
||||||
const sampleData = await SampleModel
|
const sampleData = await SampleModel
|
||||||
.aggregate([
|
.aggregate([
|
||||||
@ -705,50 +705,50 @@ async function numberGenerate (sample, req, res, next) {
|
|||||||
async function numberCheck(sample, res, next) {
|
async function numberCheck(sample, res, next) {
|
||||||
const sampleData = await SampleModel.findOne({number: sample.number})
|
const sampleData = await SampleModel.findOne({number: sample.number})
|
||||||
.lean().exec().catch(err => {next(err); return false;});
|
.lean().exec().catch(err => {next(err); return false;});
|
||||||
if (sampleData) { // found entry with sample number
|
if (sampleData) { // Found entry with sample number
|
||||||
res.status(400).json({status: 'Sample number already taken'});
|
res.status(400).json({status: 'Sample number already taken'});
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate material_id and color, returns false if invalid
|
// Validate material_id and color, returns false if invalid
|
||||||
async function materialCheck (sample, res, next) {
|
async function materialCheck (sample, res, next) {
|
||||||
const materialData = await MaterialModel.findById(sample.material_id).lean().exec().catch(err => next(err)) as any;
|
const materialData = await MaterialModel.findById(sample.material_id).lean().exec().catch(err => next(err)) as any;
|
||||||
if (materialData instanceof Error) return false;
|
if (materialData instanceof Error) return false;
|
||||||
if (!materialData) { // could not find material_id
|
if (!materialData) { // Could not find material_id
|
||||||
res.status(400).json({status: 'Material not available'});
|
res.status(400).json({status: 'Material not available'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate treatment template, returns false if invalid, otherwise template data
|
// Validate treatment template, returns false if invalid, otherwise template data
|
||||||
async function conditionCheck (condition, param, res, next, checkVersion = true) {
|
async function conditionCheck (condition, param, res, next, checkVersion = true) {
|
||||||
if (!condition.condition_template || !IdValidate.valid(condition.condition_template)) { // template id not found
|
if (!condition.condition_template || !IdValidate.valid(condition.condition_template)) { // Template id not found
|
||||||
res.status(400).json({status: 'Condition template not available'});
|
res.status(400).json({status: 'Condition template not available'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const conditionData = await ConditionTemplateModel.findById(condition.condition_template)
|
const conditionData = await ConditionTemplateModel.findById(condition.condition_template)
|
||||||
.lean().exec().catch(err => next(err)) as any;
|
.lean().exec().catch(err => next(err)) as any;
|
||||||
if (conditionData instanceof Error) return false;
|
if (conditionData instanceof Error) return false;
|
||||||
if (!conditionData) { // template not found
|
if (!conditionData) { // Template not found
|
||||||
res.status(400).json({status: 'Condition template not available'});
|
res.status(400).json({status: 'Condition template not available'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkVersion) {
|
if (checkVersion) {
|
||||||
// get all template versions and check if given is latest
|
// Get all template versions and check if given is latest
|
||||||
const conditionVersions = await ConditionTemplateModel.find({first_id: conditionData.first_id})
|
const conditionVersions = await ConditionTemplateModel.find({first_id: conditionData.first_id})
|
||||||
.sort({version: -1}).lean().exec().catch(err => next(err)) as any;
|
.sort({version: -1}).lean().exec().catch(err => next(err)) as any;
|
||||||
if (conditionVersions instanceof Error) return false;
|
if (conditionVersions instanceof Error) return false;
|
||||||
if (condition.condition_template !== conditionVersions[0]._id.toString()) { // template not latest
|
if (condition.condition_template !== conditionVersions[0]._id.toString()) { // Template not latest
|
||||||
res.status(400).json({status: 'Old template version not allowed'});
|
res.status(400).json({status: 'Old template version not allowed'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate parameters
|
// Validate parameters
|
||||||
const {error, value} =
|
const {error, value} =
|
||||||
ParametersValidate.input(_.omit(condition, 'condition_template'), conditionData.parameters, param);
|
ParametersValidate.input(_.omit(condition, 'condition_template'), conditionData.parameters, param);
|
||||||
if (error) {res400(error, res); return false;}
|
if (error) {res400(error, res); return false;}
|
||||||
@ -756,11 +756,11 @@ async function conditionCheck (condition, param, res, next, checkVersion = true)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sampleRefCheck (sample, res, next) { // validate sample_references, resolves false for invalid reference
|
function sampleRefCheck (sample, res, next) { // Validate sample_references, resolves false for invalid reference
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// there are sample_references
|
// There are sample_references
|
||||||
if (sample.notes.hasOwnProperty('sample_references') && sample.notes.sample_references.length > 0) {
|
if (sample.notes.hasOwnProperty('sample_references') && sample.notes.sample_references.length > 0) {
|
||||||
let referencesCount = sample.notes.sample_references.length; // count to keep track of running async operations
|
let referencesCount = sample.notes.sample_references.length; // Count to keep track of running async operations
|
||||||
|
|
||||||
sample.notes.sample_references.forEach(reference => {
|
sample.notes.sample_references.forEach(reference => {
|
||||||
SampleModel.findById(reference.sample_id).lean().exec((err, data) => {
|
SampleModel.findById(reference.sample_id).lean().exec((err, data) => {
|
||||||
@ -770,7 +770,7 @@ function sampleRefCheck (sample, res, next) { // validate sample_references, re
|
|||||||
return resolve(false);
|
return resolve(false);
|
||||||
}
|
}
|
||||||
referencesCount --;
|
referencesCount --;
|
||||||
if (referencesCount <= 0) { // all async requests done
|
if (referencesCount <= 0) { // All async requests done
|
||||||
resolve(true);
|
resolve(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -782,18 +782,18 @@ function sampleRefCheck (sample, res, next) { // validate sample_references, re
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function customFieldsChange (fields, amount, req) { // update custom_fields and respective quantities
|
function customFieldsChange (fields, amount, req) { // Update custom_fields and respective quantities
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}} as any, {new: true})
|
NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}} as any, {new: true})
|
||||||
.log(req).lean().exec((err, data: any) => { // check if field exists
|
.log(req).lean().exec((err, data: any) => { // Check if field exists
|
||||||
if (err) return console.error(err);
|
if (err) return console.error(err);
|
||||||
if (!data) { // new field
|
if (!data) { // New field
|
||||||
new NoteFieldModel({name: field, qty: 1}).save((err, data) => {
|
new NoteFieldModel({name: field, qty: 1}).save((err, data) => {
|
||||||
if (err) return console.error(err);
|
if (err) return console.error(err);
|
||||||
db.log(req, 'note_fields', {_id: data._id}, data.toObject());
|
db.log(req, 'note_fields', {_id: data._id}, data.toObject());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else if (data.qty <= 0) { // delete document if field is not used anymore
|
else if (data.qty <= 0) { // Delete document if field is not used anymore
|
||||||
NoteFieldModel.findOneAndDelete({name: field}).log(req).lean().exec(err => {
|
NoteFieldModel.findOneAndDelete({name: field}).log(req).lean().exec(err => {
|
||||||
if (err) return console.error(err);
|
if (err) return console.error(err);
|
||||||
});
|
});
|
||||||
@ -802,10 +802,10 @@ function customFieldsChange (fields, amount, req) { // update custom_fields and
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortQuery(filters, sortKeys, sortStartValue) { // sortKeys = ['primary key', 'secondary key']
|
function sortQuery(filters, sortKeys, sortStartValue) { // SortKeys = ['primary key', 'secondary key']
|
||||||
if (filters['from-id']) { // from-id specified
|
if (filters['from-id']) { // From-id specified
|
||||||
const ssv = sortStartValue !== undefined; // if value is not given, match for existence
|
const ssv = sortStartValue !== undefined; // If value is not given, match for existence
|
||||||
if ((filters['to-page'] === 0 && filters.sort[1] === 1) || (filters.sort[1] * filters['to-page'] > 0)) { // asc
|
if ((filters['to-page'] === 0 && filters.sort[1] === 1) || (filters.sort[1] * filters['to-page'] > 0)) { // Asc
|
||||||
return [
|
return [
|
||||||
{$match: {$or: [
|
{$match: {$or: [
|
||||||
{[sortKeys[0]]: ssv ? {$gt: sortStartValue} : {$exists: true}},
|
{[sortKeys[0]]: ssv ? {$gt: sortStartValue} : {$exists: true}},
|
||||||
@ -828,8 +828,8 @@ function sortQuery(filters, sortKeys, sortStartValue) { // sortKeys = ['primary
|
|||||||
{$sort: {[sortKeys[0]]: -1, _id: -1}}
|
{$sort: {[sortKeys[0]]: -1, _id: -1}}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
} else { // sort from beginning
|
} else { // Sort from beginning
|
||||||
return [{$sort: {[sortKeys[0]]: filters.sort[1], [sortKeys[1]]: filters.sort[1]}}]; // set _id as secondary sort
|
return [{$sort: {[sortKeys[0]]: filters.sort[1], [sortKeys[1]]: filters.sort[1]}}]; // Set _id as secondary sort
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,7 +837,7 @@ function statusQuery(filters, field) {
|
|||||||
return {$or: filters.status.map(e => ({[field]: e}))};
|
return {$or: filters.status.map(e => ({[field]: e}))};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFilterQueries (queryPtr, filters) { // returns array of match queries from given filters
|
function addFilterQueries (queryPtr, filters) { // Returns array of match queries from given filters
|
||||||
if (filters.length) {
|
if (filters.length) {
|
||||||
queryPtr.push({$match: {$and: filterQueries(filters)}});
|
queryPtr.push({$match: {$and: filterQueries(filters)}});
|
||||||
}
|
}
|
||||||
@ -845,21 +845,21 @@ function addFilterQueries (queryPtr, filters) { // returns array of match queri
|
|||||||
|
|
||||||
function filterQueries (filters) {
|
function filterQueries (filters) {
|
||||||
return filters.map(e => {
|
return filters.map(e => {
|
||||||
if (e.mode === 'or') { // allow or queries (needed for $ne added)
|
if (e.mode === 'or') { // Allow or queries (needed for $ne added)
|
||||||
return {['$' + e.mode]: e.values};
|
return {['$' + e.mode]: e.values};
|
||||||
}
|
}
|
||||||
else if (e.mode === 'stringin') {
|
else if (e.mode === 'stringin') {
|
||||||
return {[e.field]: {['$in']: [new RegExp(e.values[0])]}};
|
return {[e.field]: {['$in']: [new RegExp(e.values[0])]}};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// add filter criteria as {field: {$mode: value}}, only use first value when mode is not in/nin
|
// Add filter criteria as {field: {$mode: value}}, only use first value when mode is not in/nin
|
||||||
return {[e.field]: {['$' + e.mode]: (e.mode.indexOf('in') >= 0 ? e.values : e.values[0])}};
|
return {[e.field]: {['$' + e.mode]: (e.mode.indexOf('in') >= 0 ? e.values : e.values[0])}};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add measurements as property [template.name], if one result, array is reduced to direct values. All given templates
|
// Add measurements as property [template.name], if one result, array is reduced to direct values. All given templates
|
||||||
// must have the same name
|
// Must have the same name
|
||||||
function addMeasurements(queryPtr, templates) {
|
function addMeasurements(queryPtr, templates) {
|
||||||
queryPtr.push(
|
queryPtr.push(
|
||||||
{$addFields: {[templates[0].name]: {$let: {vars: {
|
{$addFields: {[templates[0].name]: {$let: {vars: {
|
||||||
@ -880,7 +880,7 @@ function addMeasurements(queryPtr, templates) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNotes(queryPtr) { // add note fields with default, if no notes are found
|
function addNotes(queryPtr) { // Add note fields with default, if no notes are found
|
||||||
queryPtr.push(
|
queryPtr.push(
|
||||||
{$lookup: {from: 'notes', localField: 'note_id', foreignField: '_id', as: 'notes'}},
|
{$lookup: {from: 'notes', localField: 'note_id', foreignField: '_id', as: 'notes'}},
|
||||||
{$addFields: {notes: {$cond: [
|
{$addFields: {notes: {$cond: [
|
||||||
@ -891,7 +891,7 @@ function addNotes(queryPtr) { // add note fields with default, if no notes are
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dateToOId (date) { // convert date to ObjectId
|
function dateToOId (date) { // Convert date to ObjectId
|
||||||
return mongoose.Types.ObjectId(Math.floor(date / 1000).toString(16) + '0000000000000000');
|
return mongoose.Types.ObjectId(Math.floor(date / 1000).toString(16) + '0000000000000000');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,9 +902,9 @@ async function sampleReturn (sampleData, req, res, next) {
|
|||||||
if (sampleData instanceof Error) return;
|
if (sampleData instanceof Error) return;
|
||||||
sampleData = sampleData.toObject();
|
sampleData = sampleData.toObject();
|
||||||
|
|
||||||
// deleted samples only available for dev/admin
|
// Deleted samples only available for dev/admin
|
||||||
if (sampleData.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
|
if (sampleData.status === globals.status.del && !req.auth(res, ['dev', 'admin'], 'all')) return;
|
||||||
sampleData.material = sampleData.material_id; // map data to right keys
|
sampleData.material = sampleData.material_id; // Map data to right keys
|
||||||
sampleData.material.group = sampleData.material.group_id.name;
|
sampleData.material.group = sampleData.material.group_id.name;
|
||||||
sampleData.material.supplier = sampleData.material.supplier_id.name;
|
sampleData.material.supplier = sampleData.material.supplier_id.name;
|
||||||
sampleData.user = sampleData.user_id.name;
|
sampleData.user = sampleData.user_id.name;
|
||||||
@ -912,7 +912,7 @@ async function sampleReturn (sampleData, req, res, next) {
|
|||||||
MeasurementModel.find({sample_id: sampleData._id, status: {$ne: 'deleted'}})
|
MeasurementModel.find({sample_id: sampleData._id, status: {$ne: 'deleted'}})
|
||||||
.lean().exec((err, data) => {
|
.lean().exec((err, data) => {
|
||||||
sampleData.measurements = data;
|
sampleData.measurements = data;
|
||||||
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // strip dpt values if not dev or admin
|
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // Strip dpt values if not dev or admin
|
||||||
sampleData.measurements.forEach(measurement => {
|
sampleData.measurements.forEach(measurement => {
|
||||||
if (measurement.values[globals.spectrum.dpt]) {
|
if (measurement.values[globals.spectrum.dpt]) {
|
||||||
delete measurement.values[globals.spectrum.dpt];
|
delete measurement.values[globals.spectrum.dpt];
|
||||||
|
@ -590,7 +590,7 @@ describe('/template', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// other methods should be covered by condition tests
|
// Other methods should be covered by condition tests
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/template/material', () => {
|
describe('/template/material', () => {
|
||||||
@ -656,6 +656,6 @@ describe('/template', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// other methods should be covered by condition tests
|
// Other methods should be covered by condition tests
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,10 +20,10 @@ const router = express.Router();
|
|||||||
router.get('/template/:collection(measurements|conditions|materials)', (req, res, next) => {
|
router.get('/template/:collection(measurements|conditions|materials)', (req, res, next) => {
|
||||||
if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s
|
req.params.collection = req.params.collection.replace(/s$/g, ''); // Remove trailing s
|
||||||
model(req).find({}).lean().exec((err, data) => {
|
model(req).find({}).lean().exec((err, data) => {
|
||||||
if (err) next (err);
|
if (err) next (err);
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => TemplateValidate.output(e))));
|
res.json(_.compact(data.map(e => TemplateValidate.output(e))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -49,13 +49,13 @@ router.put('/template/:collection(measurement|condition|material)/' + IdValidate
|
|||||||
const {error, value: template} = TemplateValidate.input(req.body, 'change');
|
const {error, value: template} = TemplateValidate.input(req.body, 'change');
|
||||||
if (error) return res400(error, res);
|
if (error) return res400(error, res);
|
||||||
|
|
||||||
// find given template
|
// Find given template
|
||||||
const templateRef = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
|
const templateRef = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
|
||||||
if (templateRef instanceof Error) return;
|
if (templateRef instanceof Error) return;
|
||||||
if (!templateRef) {
|
if (!templateRef) {
|
||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
// find latest version
|
// Find latest version
|
||||||
const templateData = await model(req).findOne({first_id: templateRef.first_id}).sort({version: -1})
|
const templateData = await model(req).findOne({first_id: templateRef.first_id}).sort({version: -1})
|
||||||
.lean().exec().catch(err => {next(err);}) as any;
|
.lean().exec().catch(err => {next(err);}) as any;
|
||||||
if (templateData instanceof Error) return;
|
if (templateData instanceof Error) return;
|
||||||
@ -63,8 +63,8 @@ router.put('/template/:collection(measurement|condition|material)/' + IdValidate
|
|||||||
return res.status(404).json({status: 'Not found'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed
|
if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // Data was changed
|
||||||
if (!template.parameters || _.isEqual(templateData.parameters, template.parameters)) { // only name was changed
|
if (!template.parameters || _.isEqual(templateData.parameters, template.parameters)) { // Only name was changed
|
||||||
model(req).findByIdAndUpdate(req.params.id, {name: template.name}, {new: true})
|
model(req).findByIdAndUpdate(req.params.id, {name: template.name}, {new: true})
|
||||||
.log(req).lean().exec((err, data) => {
|
.log(req).lean().exec((err, data) => {
|
||||||
if (err) next (err);
|
if (err) next (err);
|
||||||
@ -72,15 +72,15 @@ router.put('/template/:collection(measurement|condition|material)/' + IdValidate
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (template.parameters.filter((e, i) => _.isEqual(e.range, templateData.parameters[i].range)).length
|
else if (template.parameters.filter((e, i) => _.isEqual(e.range, templateData.parameters[i].range)).length
|
||||||
=== templateData.parameters.length) { // only names changed
|
=== templateData.parameters.length) { // Only names changed
|
||||||
const changedParameterNames = template.parameters.map((e, i) => ( // list of new names
|
const changedParameterNames = template.parameters.map((e, i) => ( // List of new names
|
||||||
{name: e.name, index: i, oldName: templateData.parameters[i].name}
|
{name: e.name, index: i, oldName: templateData.parameters[i].name}
|
||||||
)).filter(e => e.name !== e.oldName);
|
)).filter(e => e.name !== e.oldName);
|
||||||
|
|
||||||
// custom mappings for different collections
|
// Custom mappings for different collections
|
||||||
let targetModel; // model of the collection where the template is used
|
let targetModel; // Model of the collection where the template is used
|
||||||
let pathPrefix; // path to the parameters in use
|
let pathPrefix; // Path to the parameters in use
|
||||||
let templatePath; // complete path of the template property
|
let templatePath; // Complete path of the template property
|
||||||
switch (req.params.collection) {
|
switch (req.params.collection) {
|
||||||
case 'condition':
|
case 'condition':
|
||||||
targetModel = SampleModel;
|
targetModel = SampleModel;
|
||||||
@ -116,8 +116,8 @@ router.put('/template/:collection(measurement|condition|material)/' + IdValidate
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
template.version = templateData.version + 1; // increase version
|
template.version = templateData.version + 1; // Increase version
|
||||||
// save new template, fill with old properties
|
// Save new template, fill with old properties
|
||||||
await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {
|
await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {
|
||||||
if (err) next (err);
|
if (err) next (err);
|
||||||
db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
|
db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
|
||||||
@ -136,9 +136,9 @@ router.put('/template/:collection(measurement|condition|material)/' + IdValidate
|
|||||||
const {error, value: template} = TemplateValidate.input(req.body, 'new');
|
const {error, value: template} = TemplateValidate.input(req.body, 'new');
|
||||||
if (error) return res400(error, res);
|
if (error) return res400(error, res);
|
||||||
|
|
||||||
template._id = mongoose.Types.ObjectId(); // set reference to itself for first version of template
|
template._id = mongoose.Types.ObjectId(); // Set reference to itself for first version of template
|
||||||
template.first_id = template._id;
|
template.first_id = template._id;
|
||||||
template.version = 1; // set template version
|
template.version = 1; // Set template version
|
||||||
await new (model(req))(template).save((err, data) => {
|
await new (model(req))(template).save((err, data) => {
|
||||||
if (err) next (err);
|
if (err) next (err);
|
||||||
db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
|
db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
|
||||||
@ -149,7 +149,7 @@ router.put('/template/:collection(measurement|condition|material)/' + IdValidate
|
|||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
function model (req) { // return right template model
|
function model (req) { // Return right template model
|
||||||
switch (req.params.collection) {
|
switch (req.params.collection) {
|
||||||
case 'condition': return ConditionTemplateModel
|
case 'condition': return ConditionTemplateModel
|
||||||
case 'measurement': return MeasurementTemplateModel
|
case 'measurement': return MeasurementTemplateModel
|
||||||
|
@ -18,12 +18,12 @@ router.get('/users', (req, res) => {
|
|||||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||||
|
|
||||||
UserModel.find({}).lean().exec( (err, data:any) => {
|
UserModel.find({}).lean().exec( (err, data:any) => {
|
||||||
// validate all and filter null values from validation errors
|
// Validate all and filter null values from validation errors
|
||||||
res.json(_.compact(data.map(e => UserValidate.output(e, 'admin'))));
|
res.json(_.compact(data.map(e => UserValidate.output(e, 'admin'))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
|
// This path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
|
||||||
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
||||||
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
||||||
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
||||||
@ -33,7 +33,7 @@ router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
|||||||
UserModel.findOne({name: username}).lean().exec( (err, data:any) => {
|
UserModel.findOne({name: username}).lean().exec( (err, data:any) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data) {
|
if (data) {
|
||||||
res.json(UserValidate.output(data)); // validate all and filter null values from validation errors
|
res.json(UserValidate.output(data)); // Validate all and filter null values from validation errors
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
res.status(404).json({status: 'Not found'});
|
res.status(404).json({status: 'Not found'});
|
||||||
@ -41,7 +41,7 @@ router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
|
// This path matches /user, /user/ and /user/xxx, but not /user/key or user/new
|
||||||
router.put('/user:username([/](?!key|new|restore).?*|/?)', async (req, res, next) => {
|
router.put('/user:username([/](?!key|new|restore).?*|/?)', async (req, res, next) => {
|
||||||
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ router.put('/user:username([/](?!key|new|restore).?*|/?)', async (req, res, next
|
|||||||
user.pass = bcrypt.hashSync(user.pass, 10);
|
user.pass = bcrypt.hashSync(user.pass, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that user does not already exist if new name was specified
|
// Check that user does not already exist if new name was specified
|
||||||
if (user.hasOwnProperty('name') && user.name !== username) {
|
if (user.hasOwnProperty('name') && user.name !== username) {
|
||||||
if (!await usernameCheck(user.name, res, next)) return;
|
if (!await usernameCheck(user.name, res, next)) return;
|
||||||
}
|
}
|
||||||
@ -65,13 +65,13 @@ router.put('/user:username([/](?!key|new|restore).?*|/?)', async (req, res, next
|
|||||||
if (!await modelsCheck(user.models, res, next)) return;
|
if (!await modelsCheck(user.models, res, next)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get current mail address to compare to given address
|
// Get current mail address to compare to given address
|
||||||
const oldUserData = await UserModel.findOne({name: username}).lean().exec().catch(err => next(err));
|
const oldUserData = await UserModel.findOne({name: username}).lean().exec().catch(err => next(err));
|
||||||
|
|
||||||
await UserModel.findOneAndUpdate({name: username}, user, {new: true}).log(req).lean().exec( (err, data:any) => {
|
await UserModel.findOneAndUpdate({name: username}, user, {new: true}).log(req).lean().exec( (err, data:any) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.email !== oldUserData.email) { // mail address was changed, send notice to old address
|
if (data.email !== oldUserData.email) { // Mail address was changed, send notice to old address
|
||||||
Mail.send(oldUserData.email, 'Email change in your DeFinMa database account',
|
Mail.send(oldUserData.email, 'Email change in your DeFinMa database account',
|
||||||
'Hi, <br><br> Your email address of your DeFinMa account was changed to ' + data.mail +
|
'Hi, <br><br> Your email address of your DeFinMa account was changed to ' + data.mail +
|
||||||
'<br><br>If you actually did this, just delete this email.' +
|
'<br><br>If you actually did this, just delete this email.' +
|
||||||
@ -87,7 +87,7 @@ router.put('/user:username([/](?!key|new|restore).?*|/?)', async (req, res, next
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
|
// This path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
|
||||||
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
||||||
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
||||||
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return;
|
||||||
@ -132,19 +132,19 @@ router.get('/user/key', (req, res, next) => {
|
|||||||
router.post('/user/new', async (req, res, next) => {
|
router.post('/user/new', async (req, res, next) => {
|
||||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||||
|
|
||||||
// validate input
|
// Validate input
|
||||||
const {error, value: user} = UserValidate.input(req.body, 'new');
|
const {error, value: user} = UserValidate.input(req.body, 'new');
|
||||||
if (error) return res400(error, res);
|
if (error) return res400(error, res);
|
||||||
|
|
||||||
// check that user does not already exist
|
// Check that user does not already exist
|
||||||
if (!await usernameCheck(user.name, res, next)) return;
|
if (!await usernameCheck(user.name, res, next)) return;
|
||||||
if (!await modelsCheck(user.models, res, next)) return;
|
if (!await modelsCheck(user.models, res, next)) return;
|
||||||
|
|
||||||
user.key = mongoose.Types.ObjectId(); // use object id as unique API key
|
user.key = mongoose.Types.ObjectId(); // Use object id as unique API key
|
||||||
user.status = globals.status.new;
|
user.status = globals.status.new;
|
||||||
bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing
|
bcrypt.hash(user.pass, 10, (err, hash) => { // Password hashing
|
||||||
user.pass = hash;
|
user.pass = hash;
|
||||||
new UserModel(user).save((err, data) => { // store user
|
new UserModel(user).save((err, data) => { // Store user
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
db.log(req, 'users', {_id: data._id}, data.toObject());
|
db.log(req, 'users', {_id: data._id}, data.toObject());
|
||||||
res.json(UserValidate.output(data.toObject()));
|
res.json(UserValidate.output(data.toObject()));
|
||||||
@ -153,18 +153,18 @@ router.post('/user/new', async (req, res, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/user/passreset', (req, res, next) => {
|
router.post('/user/passreset', (req, res, next) => {
|
||||||
// check if user/email combo exists
|
// Check if user/email combo exists
|
||||||
UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
|
UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data.length === 1) { // it exists
|
if (data.length === 1) { // It exists
|
||||||
const newPass = Math.random().toString(36).substring(2); // generate temporary password
|
const newPass = Math.random().toString(36).substring(2); // Generate temporary password
|
||||||
bcrypt.hash(newPass, 10, (err, hash) => { // password hashing
|
bcrypt.hash(newPass, 10, (err, hash) => { // Password hashing
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}).log(req).exec(err => { // write new password
|
UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}).log(req).exec(err => { // Write new password
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// send email
|
// Send email
|
||||||
Mail.send(data[0].email, 'Your new password for the DeFinMa database',
|
Mail.send(data[0].email, 'Your new password for the DeFinMa database',
|
||||||
'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '' +
|
'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '' +
|
||||||
'<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.' +
|
'<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.' +
|
||||||
@ -184,9 +184,9 @@ router.post('/user/passreset', (req, res, next) => {
|
|||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
function getUsername (req, res) { // returns username or false if action is not allowed
|
function getUsername (req, res) { // Returns username or false if action is not allowed
|
||||||
req.params.username = req.params[0]; // because of path regex
|
req.params.username = req.params[0]; // Because of path regex
|
||||||
if (req.params.username !== undefined) { // different username than request user
|
if (req.params.username !== undefined) { // Different username than request user
|
||||||
if (!req.auth(res, ['admin'], 'basic')) return false;
|
if (!req.auth(res, ['admin'], 'basic')) return false;
|
||||||
return req.params.username;
|
return req.params.username;
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ else {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function usernameCheck (name, res, next) { // check if username is already taken
|
async function usernameCheck (name, res, next) { // Check if username is already taken
|
||||||
const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
|
const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
|
||||||
if (userData instanceof Error) return false;
|
if (userData instanceof Error) return false;
|
||||||
if (userData || UserValidate.isSpecialName(name)) {
|
if (userData || UserValidate.isSpecialName(name)) {
|
||||||
@ -205,7 +205,7 @@ if (userData || UserValidate.isSpecialName(name)) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function modelsCheck (models, res, next) { // check if model ids exist, returns false on error
|
async function modelsCheck (models, res, next) { // Check if model ids exist, returns false on error
|
||||||
let result = true;
|
let result = true;
|
||||||
for (let i in models) {
|
for (let i in models) {
|
||||||
const model = await ModelModel.findOne({'models._id': mongoose.Types.ObjectId(models[i])})
|
const model = await ModelModel.findOne({'models._id': mongoose.Types.ObjectId(models[i])})
|
||||||
|
@ -6,11 +6,11 @@ export default class IdValidate {
|
|||||||
.length(24)
|
.length(24)
|
||||||
.messages({'string.pattern.base': 'Invalid object id'});
|
.messages({'string.pattern.base': 'Invalid object id'});
|
||||||
|
|
||||||
static get () { // return joi validation
|
static get () { // Return joi validation
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static valid (id) { // validate id
|
static valid (id) { // Validate id
|
||||||
return this.id.validate(id).error === undefined;
|
return this.id.validate(id).error === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,13 +18,13 @@ export default class IdValidate {
|
|||||||
return ':id([0-9a-f]{24})';
|
return ':id([0-9a-f]{24})';
|
||||||
}
|
}
|
||||||
|
|
||||||
static stringify (data) { // convert all ObjectID objects to plain strings
|
static stringify (data) { // Convert all ObjectID objects to plain strings
|
||||||
Object.keys(data).forEach(key => {
|
Object.keys(data).forEach(key => {
|
||||||
// stringify id
|
// Stringify id
|
||||||
if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
|
if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
|
||||||
data[key] = data[key].toString();
|
data[key] = data[key].toString();
|
||||||
}
|
}
|
||||||
else if (typeof data[key] === 'object' && data[key] !== null) { // deeper into recursion
|
else if (typeof data[key] === 'object' && data[key] !== null) { // Deeper into recursion
|
||||||
data[key] = this.stringify(data[key]);
|
data[key] = this.stringify(data[key]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import Joi from 'joi';
|
|||||||
import IdValidate from './id';
|
import IdValidate from './id';
|
||||||
import globals from '../../globals';
|
import globals from '../../globals';
|
||||||
|
|
||||||
export default class MaterialValidate { // validate input for material
|
export default class MaterialValidate { // Validate input for material
|
||||||
private static material = {
|
private static material = {
|
||||||
name: Joi.string()
|
name: Joi.string()
|
||||||
.max(128),
|
.max(128),
|
||||||
@ -26,7 +26,7 @@ export default class MaterialValidate { // validate input for material
|
|||||||
.valid(...Object.values(globals.status))
|
.valid(...Object.values(globals.status))
|
||||||
};
|
};
|
||||||
|
|
||||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
static input (data, param) { // Validate input, set param to 'new' to make all attributes required
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
name: this.material.name.required(),
|
name: this.material.name.required(),
|
||||||
@ -50,7 +50,7 @@ export default class MaterialValidate { // validate input for material
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static output (data, status = false) { // validate output and strip unwanted properties, returns null if not valid
|
static output (data, status = false) { // Validate output and strip unwanted properties, returns null if not valid
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
data.group = data.group_id.name;
|
data.group = data.group_id.name;
|
||||||
data.supplier = data.supplier_id.name;
|
data.supplier = data.supplier_id.name;
|
||||||
@ -69,17 +69,17 @@ export default class MaterialValidate { // validate input for material
|
|||||||
return error !== undefined? null : value;
|
return error !== undefined? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static outputGroups (data) {// validate groups output and strip unwanted properties, returns null if not valid
|
static outputGroups (data) {// Validate groups output and strip unwanted properties, returns null if not valid
|
||||||
const {value, error} = this.material.group.validate(data, {stripUnknown: true});
|
const {value, error} = this.material.group.validate(data, {stripUnknown: true});
|
||||||
return error !== undefined? null : value;
|
return error !== undefined? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static outputSuppliers (data) {// validate suppliers output and strip unwanted properties, returns null if not valid
|
static outputSuppliers (data) {// Validate suppliers output and strip unwanted properties, returns null if not valid
|
||||||
const {value, error} = this.material.supplier.validate(data, {stripUnknown: true});
|
const {value, error} = this.material.supplier.validate(data, {stripUnknown: true});
|
||||||
return error !== undefined? null : value;
|
return error !== undefined? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static outputV() { // return output validator
|
static outputV() { // Return output validator
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
_id: IdValidate.get(),
|
_id: IdValidate.get(),
|
||||||
name: this.material.name,
|
name: this.material.name,
|
||||||
@ -92,7 +92,7 @@ export default class MaterialValidate { // validate input for material
|
|||||||
|
|
||||||
static query (data, dev = false) {
|
static query (data, dev = false) {
|
||||||
const acceptedStatuses = [globals.status.val, globals.status.new];
|
const acceptedStatuses = [globals.status.val, globals.status.new];
|
||||||
if (dev) { // dev and admin can also access deleted samples
|
if (dev) { // Dev and admin can also access deleted samples
|
||||||
acceptedStatuses.push(globals.status.del)
|
acceptedStatuses.push(globals.status.del)
|
||||||
}
|
}
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
|
@ -11,14 +11,14 @@ export default class MeasurementValidate {
|
|||||||
Joi.string().max(128),
|
Joi.string().max(128),
|
||||||
Joi.number(),
|
Joi.number(),
|
||||||
Joi.boolean(),
|
Joi.boolean(),
|
||||||
Joi.array().items(Joi.array().items(Joi.number())), // for spectra
|
Joi.array().items(Joi.array().items(Joi.number())), // For spectra
|
||||||
Joi.array()
|
Joi.array()
|
||||||
)
|
)
|
||||||
.allow(null)
|
.allow(null)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
static input (data, param) { // Validate input, set param to 'new' to make all attributes required
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
sample_id: IdValidate.get().required(),
|
sample_id: IdValidate.get().required(),
|
||||||
@ -36,10 +36,10 @@ export default class MeasurementValidate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate output and strip unwanted properties, returns null if not valid
|
// Validate output and strip unwanted properties, returns null if not valid
|
||||||
static output (data, req, status = false) {
|
static output (data, req, status = false) {
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
// spectral data not allowed for read/write users
|
// Spectral data not allowed for read/write users
|
||||||
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0 && data.values[globals.spectrum.dpt]) {
|
if (['dev', 'admin'].indexOf(req.authDetails.level) < 0 && data.values[globals.spectrum.dpt]) {
|
||||||
delete data.values[globals.spectrum.dpt];
|
delete data.values[globals.spectrum.dpt];
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ export default class MeasurementValidate {
|
|||||||
return error !== undefined? null : value;
|
return error !== undefined? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static outputV() { // return output validator
|
static outputV() { // Return output validator
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
_id: IdValidate.get(),
|
_id: IdValidate.get(),
|
||||||
sample_id: IdValidate.get(),
|
sample_id: IdValidate.get(),
|
||||||
|
@ -2,7 +2,7 @@ import Joi from 'joi';
|
|||||||
import IdValidate from './id';
|
import IdValidate from './id';
|
||||||
|
|
||||||
|
|
||||||
export default class ModelValidate { // validate input for model
|
export default class ModelValidate { // Validate input for model
|
||||||
private static model = {
|
private static model = {
|
||||||
group: Joi.string()
|
group: Joi.string()
|
||||||
.disallow('file')
|
.disallow('file')
|
||||||
@ -20,11 +20,11 @@ export default class ModelValidate { // validate input for model
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
static input (data) { // validate input
|
static input (data) { // Validate input
|
||||||
return this.model.model.required().validate(data);
|
return this.model.model.required().validate(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static output (data) { // validate output and strip unwanted properties, returns null if not valid
|
static output (data) { // Validate output and strip unwanted properties, returns null if not valid
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
const {value, error} = Joi.object({
|
const {value, error} = Joi.object({
|
||||||
group: this.model.group,
|
group: this.model.group,
|
||||||
|
@ -8,7 +8,7 @@ export default class NoteFieldValidate {
|
|||||||
qty: Joi.number()
|
qty: Joi.number()
|
||||||
};
|
};
|
||||||
|
|
||||||
static output (data) { // validate output and strip unwanted properties, returns null if not valid
|
static output (data) { // Validate output and strip unwanted properties, returns null if not valid
|
||||||
const {value, error} = Joi.object({
|
const {value, error} = Joi.object({
|
||||||
name: this.note_field.name,
|
name: this.note_field.name,
|
||||||
qty: this.note_field.qty
|
qty: this.note_field.qty
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
|
|
||||||
export default class ParametersValidate {
|
export default class ParametersValidate {
|
||||||
// data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed)
|
// Data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed)
|
||||||
static input (data, parameters, param) {
|
static input (data, parameters, param) {
|
||||||
let joiObject = {};
|
let joiObject = {};
|
||||||
parameters.forEach(parameter => {
|
parameters.forEach(parameter => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// respond with 400 and include error details from the joi validation
|
// Respond with 400 and include error details from the joi validation
|
||||||
|
|
||||||
export default function res400 (error, res) {
|
export default function res400 (error, res) {
|
||||||
res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
|
res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import IdValidate from './id';
|
import IdValidate from './id';
|
||||||
|
|
||||||
export default class RootValidate { // validate input for root methods
|
export default class RootValidate { // Validate input for root methods
|
||||||
private static changelog = {
|
private static changelog = {
|
||||||
timestamp: Joi.date()
|
timestamp: Joi.date()
|
||||||
.iso()
|
.iso()
|
||||||
|
@ -58,7 +58,7 @@ export default class SampleValidate {
|
|||||||
.valid(...Object.values(globals.status))
|
.valid(...Object.values(globals.status))
|
||||||
};
|
};
|
||||||
|
|
||||||
static readonly sampleKeys = [ // keys which can be found in the sample directly
|
static readonly sampleKeys = [ // Keys which can be found in the sample directly
|
||||||
'_id',
|
'_id',
|
||||||
'color',
|
'color',
|
||||||
'number',
|
'number',
|
||||||
@ -102,7 +102,7 @@ export default class SampleValidate {
|
|||||||
`measurements.${globals.spectrum.spectrum}.${globals.spectrum.dpt}`,
|
`measurements.${globals.spectrum.spectrum}.${globals.spectrum.dpt}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
static input (data, param) { // Validate input, set param to 'new' to make all attributes required
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
color: this.sample.color.required(),
|
color: this.sample.color.required(),
|
||||||
@ -139,7 +139,7 @@ export default class SampleValidate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate output and strip unwanted properties, returns null if not valid
|
// Validate output and strip unwanted properties, returns null if not valid
|
||||||
static output (data, param = 'refs+added', additionalParams = []) {
|
static output (data, param = 'refs+added', additionalParams = []) {
|
||||||
if (param === 'refs+added') {
|
if (param === 'refs+added') {
|
||||||
param = 'refs';
|
param = 'refs';
|
||||||
@ -200,13 +200,13 @@ export default class SampleValidate {
|
|||||||
}
|
}
|
||||||
catch (ignore) {}
|
catch (ignore) {}
|
||||||
data.filters[i] = JSON.parse(data.filters[i]);
|
data.filters[i] = JSON.parse(data.filters[i]);
|
||||||
data.filters[i].values = data.filters[i].values.map(e => { // validate filter values
|
data.filters[i].values = data.filters[i].values.map(e => { // Validate filter values
|
||||||
if (e === null) { // null values are always allowed
|
if (e === null) { // Null values are always allowed
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let validator;
|
let validator;
|
||||||
let field = data.filters[i].field;
|
let field = data.filters[i].field;
|
||||||
if (/material\./.test(field)) { // select right validation model
|
if (/material\./.test(field)) { // Select right validation model
|
||||||
validator = MaterialValidate.outputV().append({
|
validator = MaterialValidate.outputV().append({
|
||||||
number: Joi.string().max(128).allow(''),
|
number: Joi.string().max(128).allow(''),
|
||||||
properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128).allow(''))
|
properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128).allow(''))
|
||||||
@ -240,7 +240,7 @@ export default class SampleValidate {
|
|||||||
validator = Joi.object(this.sample);
|
validator = Joi.object(this.sample);
|
||||||
}
|
}
|
||||||
const {value, error} = validator.validate({[field]: e});
|
const {value, error} = validator.validate({[field]: e});
|
||||||
if (error) throw error; // reject invalid values
|
if (error) throw error; // Reject invalid values
|
||||||
return value[field];
|
return value[field];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -250,7 +250,7 @@ export default class SampleValidate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const acceptedStatuses = [globals.status.val, globals.status.new];
|
const acceptedStatuses = [globals.status.val, globals.status.new];
|
||||||
if (dev) { // dev and admin can also access deleted samples
|
if (dev) { // Dev and admin can also access deleted samples
|
||||||
acceptedStatuses.push(globals.status.del)
|
acceptedStatuses.push(globals.status.del)
|
||||||
}
|
}
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
|
@ -39,7 +39,7 @@ export default class TemplateValidate {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
static input (data, param) { // Validate input, set param to 'new' to make all attributes required
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
name: this.template.name.required(),
|
name: this.template.name.required(),
|
||||||
@ -57,7 +57,7 @@ export default class TemplateValidate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static output (data) { // validate output and strip unwanted properties, returns null if not valid
|
static output (data) { // Validate output and strip unwanted properties, returns null if not valid
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
const {value, error} = Joi.object({
|
const {value, error} = Joi.object({
|
||||||
_id: IdValidate.get(),
|
_id: IdValidate.get(),
|
||||||
|
@ -3,7 +3,7 @@ import globals from '../../globals';
|
|||||||
|
|
||||||
import IdValidate from './id';
|
import IdValidate from './id';
|
||||||
|
|
||||||
export default class UserValidate { // validate input for user
|
export default class UserValidate { // Validate input for user
|
||||||
private static user = {
|
private static user = {
|
||||||
name: Joi.string()
|
name: Joi.string()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
@ -40,9 +40,9 @@ export default class UserValidate { // validate input for user
|
|||||||
.valid(...Object.values(globals.status))
|
.valid(...Object.values(globals.status))
|
||||||
};
|
};
|
||||||
|
|
||||||
private static specialUsernames: string[] = ['admin', 'user', 'key', 'new', 'passreset']; // names a user cannot take
|
private static specialUsernames: string[] = ['admin', 'user', 'key', 'new', 'passreset']; // Names a user cannot take
|
||||||
|
|
||||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
static input (data, param) { // Validate input, set param to 'new' to make all attributes required
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
name: this.user.name.required(),
|
name: this.user.name.required(),
|
||||||
@ -79,7 +79,7 @@ export default class UserValidate { // validate input for user
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static output (data, param = '') { // validate output and strip unwanted properties, returns null if not valid
|
static output (data, param = '') { // Validate output and strip unwanted properties, returns null if not valid
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
const validate: {[key: string]: object} = {
|
const validate: {[key: string]: object} = {
|
||||||
_id: IdValidate.get(),
|
_id: IdValidate.get(),
|
||||||
@ -97,7 +97,7 @@ export default class UserValidate { // validate input for user
|
|||||||
return error !== undefined? null : value;
|
return error !== undefined? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static isSpecialName (name) { // true if name belongs to special names
|
static isSpecialName (name) { // True if name belongs to special names
|
||||||
return this.specialUsernames.indexOf(name) > -1;
|
return this.specialUsernames.indexOf(name) > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import IdValidate from '../routes/validate/id';
|
|||||||
|
|
||||||
|
|
||||||
export default class TestHelper {
|
export default class TestHelper {
|
||||||
public static auth = { // test user credentials
|
public static auth = { // Test user credentials
|
||||||
admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'},
|
admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'},
|
||||||
janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'},
|
janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'},
|
||||||
user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'},
|
user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'},
|
||||||
@ -15,7 +15,7 @@ export default class TestHelper {
|
|||||||
customer: {pass: 'Xyz890*)', key: '000000000000000000001005', id: '000000000000000000000005'}
|
customer: {pass: 'Xyz890*)', key: '000000000000000000001005', id: '000000000000000000000005'}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static res = { // default responses
|
public static res = { // Default responses
|
||||||
400: {status: 'Bad request'},
|
400: {status: 'Bad request'},
|
||||||
401: {status: 'Unauthorized'},
|
401: {status: 'Unauthorized'},
|
||||||
403: {status: 'Forbidden'},
|
403: {status: 'Forbidden'},
|
||||||
@ -30,27 +30,27 @@ export default class TestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static beforeEach (server, done) {
|
static beforeEach (server, done) {
|
||||||
// delete cached server code except models as these are needed in the testing files as well
|
// Delete cached server code except models as these are needed in the testing files as well
|
||||||
Object.keys(require.cache).filter(e => /API\\dist\\(?!(models|db|test))/.test(e)).forEach(key => {
|
Object.keys(require.cache).filter(e => /API\\dist\\(?!(models|db|test))/.test(e)).forEach(key => {
|
||||||
delete require.cache[key]; // prevent loading from cache
|
delete require.cache[key]; // Prevent loading from cache
|
||||||
});
|
});
|
||||||
server = require('../index');
|
server = require('../index');
|
||||||
db.drop(err => { // reset database
|
db.drop(err => { // Reset database
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
db.loadJson(require('./db.json'), done);
|
db.loadJson(require('./db.json'), done);
|
||||||
});
|
});
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
// options in form: {method, url, contentType, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res,
|
// Options in form: {method, url, contentType, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res,
|
||||||
// default (set to false if you want to dismiss default .end handling)}
|
// Default (set to false if you want to dismiss default .end handling)}
|
||||||
static request (server, done, options) {
|
static request (server, done, options) {
|
||||||
let st = supertest(server);
|
let st = supertest(server);
|
||||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key
|
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // Resolve API key
|
||||||
options.url += '?key=' +
|
options.url += '?key=' +
|
||||||
(this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
|
(this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
|
||||||
}
|
}
|
||||||
switch (options.method) { // http method
|
switch (options.method) { // Http method
|
||||||
case 'get':
|
case 'get':
|
||||||
st = st.get(options.url)
|
st = st.get(options.url)
|
||||||
break;
|
break;
|
||||||
@ -64,16 +64,16 @@ export default class TestHelper {
|
|||||||
st = st.delete(options.url)
|
st = st.delete(options.url)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (options.hasOwnProperty('reqType')) { // request body
|
if (options.hasOwnProperty('reqType')) { // Request body
|
||||||
st = st.type(options.reqType);
|
st = st.type(options.reqType);
|
||||||
}
|
}
|
||||||
if (options.hasOwnProperty('req')) { // request body
|
if (options.hasOwnProperty('req')) { // Request body
|
||||||
st = st.send(options.req);
|
st = st.send(options.req);
|
||||||
}
|
}
|
||||||
if (options.hasOwnProperty('reqContentType')) { // request body
|
if (options.hasOwnProperty('reqContentType')) { // Request body
|
||||||
st = st.set('Content-Type', options.reqContentType);
|
st = st.set('Content-Type', options.reqContentType);
|
||||||
}
|
}
|
||||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) { // resolve basic auth
|
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) { // Resolve basic auth
|
||||||
if (this.auth.hasOwnProperty(options.auth.basic)) {
|
if (this.auth.hasOwnProperty(options.auth.basic)) {
|
||||||
st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
|
st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
|
||||||
}
|
}
|
||||||
@ -87,26 +87,26 @@ export default class TestHelper {
|
|||||||
else {
|
else {
|
||||||
st = st.expect('Content-type', /json/).expect(options.httpStatus);
|
st = st.expect('Content-type', /json/).expect(options.httpStatus);
|
||||||
}
|
}
|
||||||
if (options.hasOwnProperty('res')) { // evaluate result
|
if (options.hasOwnProperty('res')) { // Evaluate result
|
||||||
return st.end((err, res) => {
|
return st.end((err, res) => {
|
||||||
if (err) return done (err);
|
if (err) return done (err);
|
||||||
should(res.body).be.eql(options.res);
|
should(res.body).be.eql(options.res);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) { // evaluate default results
|
else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) { // Evaluate default results
|
||||||
return st.end((err, res) => {
|
return st.end((err, res) => {
|
||||||
if (err) return done (err);
|
if (err) return done (err);
|
||||||
should(res.body).be.eql(this.res[options.httpStatus]);
|
should(res.body).be.eql(this.res[options.httpStatus]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)}
|
// Check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)}
|
||||||
else if (options.hasOwnProperty('log')) {
|
else if (options.hasOwnProperty('log')) {
|
||||||
return st.end(err => {
|
return st.end(err => {
|
||||||
if (err) return done (err);
|
if (err) return done (err);
|
||||||
ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0)
|
ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0)
|
||||||
.lean().exec((err, data) => { // latest entry
|
.lean().exec((err, data) => { // Latest entry
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
should(data).have.only.keys('_id', 'action', 'collection_name', 'conditions', 'data', 'user_id', '__v');
|
should(data).have.only.keys('_id', 'action', 'collection_name', 'conditions', 'data', 'user_id', '__v');
|
||||||
should(data).have.property('action', options.method.toUpperCase() + ' ' + options.url);
|
should(data).have.property('action', options.method.toUpperCase() + ' ' + options.url);
|
||||||
@ -132,7 +132,7 @@ export default class TestHelper {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else { // return object to do .end() manually
|
else { // Return object to do .end() manually
|
||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import db from '../db';
|
import db from '../db';
|
||||||
|
|
||||||
// script to load test db into dev db for a clean start
|
// Script to load test db into dev db for a clean start
|
||||||
|
|
||||||
db.connect('dev', () => {
|
db.connect('dev', () => {
|
||||||
console.info('dropping data...');
|
console.info('dropping data...');
|
||||||
db.drop(() => { // reset database
|
db.drop(() => { // Reset database
|
||||||
console.info('loading data...');
|
console.info('loading data...');
|
||||||
db.loadJson(require('./db.json'), () => {
|
db.loadJson(require('./db.json'), () => {
|
||||||
console.info('done');
|
console.info('done');
|
||||||
|
Reference in New Issue
Block a user