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