2020-05-06 14:39:04 +02:00
import express from 'express' ;
2020-05-12 17:15:36 +02:00
import _ from 'lodash' ;
2020-05-06 14:39:04 +02:00
import SampleValidate from './validate/sample' ;
import NoteFieldValidate from './validate/note_field' ;
2020-05-07 21:55:29 +02:00
import res400 from './validate/res400' ;
2020-05-06 14:39:04 +02:00
import SampleModel from '../models/sample'
2020-05-27 14:31:17 +02:00
import MeasurementModel from '../models/measurement' ;
2020-05-06 14:39:04 +02:00
import MaterialModel from '../models/material' ;
import NoteModel from '../models/note' ;
import NoteFieldModel from '../models/note_field' ;
2020-05-07 21:55:29 +02:00
import IdValidate from './validate/id' ;
2020-05-28 13:05:00 +02:00
import mongoose from 'mongoose' ;
2020-05-27 14:31:17 +02:00
import ConditionTemplateModel from '../models/condition_template' ;
import ParametersValidate from './validate/parameters' ;
import globals from '../globals' ;
2020-05-06 14:39:04 +02:00
const router = express . Router ( ) ;
router . get ( '/samples' , ( req , res , next ) = > {
if ( ! req . auth ( res , [ 'read' , 'write' , 'maintain' , 'dev' , 'admin' ] , 'all' ) ) return ;
2020-05-27 14:31:17 +02:00
SampleModel . find ( { status : globals.status.validated } ) . lean ( ) . exec ( ( err , data ) = > {
2020-05-06 14:39:04 +02:00
if ( err ) return next ( err ) ;
2020-05-12 17:15:36 +02:00
res . json ( _ . compact ( data . map ( e = > SampleValidate . output ( e ) ) ) ) ; // validate all and filter null values from validation errors
2020-05-06 14:39:04 +02:00
} )
} ) ;
2020-05-28 17:05:23 +02:00
router . get ( '/samples/:state(new|deleted)' , ( req , res , next ) = > {
2020-05-18 14:47:22 +02:00
if ( ! req . auth ( res , [ 'maintain' , 'admin' ] , 'basic' ) ) return ;
2020-05-28 17:05:23 +02:00
SampleModel . find ( { status : globals.status [ req . params . state ] } ) . lean ( ) . exec ( ( err , data ) = > {
2020-05-18 14:47:22 +02:00
if ( err ) return next ( err ) ;
res . json ( _ . compact ( data . map ( e = > SampleValidate . output ( e ) ) ) ) ; // validate all and filter null values from validation errors
2020-05-27 14:31:17 +02:00
} ) ;
} ) ;
router . get ( '/sample/' + IdValidate . parameter ( ) , ( req , res , next ) = > {
if ( ! req . auth ( res , [ 'read' , 'write' , 'maintain' , 'dev' , 'admin' ] , 'all' ) ) return ;
2020-05-29 11:06:39 +02:00
SampleModel . findById ( req . params . id ) . populate ( 'material_id' ) . populate ( 'user_id' , 'name' ) . populate ( 'note_id' ) . exec ( async ( err , sampleData : any ) = > {
2020-05-27 14:31:17 +02:00
if ( err ) return next ( err ) ;
if ( sampleData ) {
2020-05-29 11:06:39 +02:00
await sampleData . populate ( 'material_id.group_id' ) . populate ( 'material_id.supplier_id' ) . execPopulate ( ) . catch ( err = > next ( err ) ) ;
if ( sampleData instanceof Error ) return ;
sampleData = sampleData . toObject ( ) ;
2020-05-28 14:11:19 +02:00
if ( sampleData . status === globals . status . deleted && ! req . auth ( res , [ 'maintain' , 'admin' ] , 'all' ) ) return ; // deleted samples only available for maintain/admin
2020-05-27 14:31:17 +02:00
sampleData . material = sampleData . material_id ; // map data to right keys
2020-05-29 11:06:39 +02:00
sampleData . material . group = sampleData . material . group_id . name ;
sampleData . material . supplier = sampleData . material . supplier_id . name ;
2020-05-27 14:31:17 +02:00
sampleData . user = sampleData . user_id . name ;
sampleData . notes = sampleData . note_id ? sampleData . note_id : { } ;
MeasurementModel . find ( { sample_id : mongoose.Types.ObjectId ( req . params . id ) } ) . lean ( ) . exec ( ( err , data ) = > {
sampleData . measurements = data ;
res . json ( SampleValidate . output ( sampleData , 'details' ) ) ;
} ) ;
}
else {
res . status ( 404 ) . json ( { status : 'Not found' } ) ;
}
} ) ;
2020-05-18 14:47:22 +02:00
} ) ;
2020-05-07 21:55:29 +02:00
router . put ( '/sample/' + IdValidate . parameter ( ) , ( req , res , next ) = > {
2020-05-06 14:39:04 +02:00
if ( ! req . auth ( res , [ 'write' , 'maintain' , 'dev' , 'admin' ] , 'basic' ) ) return ;
2020-05-07 21:55:29 +02:00
const { error , value : sample } = SampleValidate . input ( req . body , 'change' ) ;
if ( error ) return res400 ( error , res ) ;
2020-05-06 14:39:04 +02:00
2020-05-07 21:55:29 +02:00
SampleModel . findById ( req . params . id ) . lean ( ) . exec ( async ( err , sampleData : any ) = > { // check if id exists
2020-05-06 14:39:04 +02:00
if ( err ) return next ( err ) ;
2020-05-07 21:55:29 +02:00
if ( ! sampleData ) {
return res . status ( 404 ) . json ( { status : 'Not found' } ) ;
2020-05-06 14:39:04 +02:00
}
2020-05-28 12:40:37 +02:00
if ( sampleData . status === globals . status . deleted ) {
return res . status ( 403 ) . json ( { status : 'Forbidden' } ) ;
}
2020-05-18 14:47:22 +02:00
2020-05-07 21:55:29 +02:00
// only maintain and admin are allowed to edit other user's data
if ( sampleData . user_id . toString ( ) !== req . authDetails . id && ! req . auth ( res , [ 'maintain' , 'admin' ] , 'basic' ) ) return ;
if ( sample . hasOwnProperty ( 'material_id' ) ) {
if ( ! await materialCheck ( sample , res , next ) ) return ;
}
else if ( sample . hasOwnProperty ( 'color' ) ) {
if ( ! await materialCheck ( sample , res , next , sampleData . material_id ) ) return ;
}
2020-05-27 14:31:17 +02:00
if ( sample . hasOwnProperty ( 'condition' ) && ! ( _ . isEmpty ( sample . condition ) && _ . isEmpty ( sampleData . condition ) ) ) { // do not execute check if condition is and was empty
if ( ! await conditionCheck ( sample . condition , 'change' , res , next ) ) return ;
}
2020-05-14 15:36:47 +02:00
if ( sample . hasOwnProperty ( 'notes' ) ) {
let newNotes = true ;
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 ;
if ( data instanceof Error ) return ;
2020-05-18 14:47:22 +02:00
newNotes = ! _ . isEqual ( _ . pick ( IdValidate . stringify ( data ) , _ . keys ( sample . notes ) ) , sample . notes ) ; // check if notes were changed
2020-05-14 15:36:47 +02:00
if ( newNotes ) {
if ( data . hasOwnProperty ( 'custom_fields' ) ) { // update note_fields
customFieldsChange ( Object . keys ( data . custom_fields ) , - 1 ) ;
}
2020-05-18 14:47:22 +02:00
await NoteModel . findByIdAndDelete ( sampleData . note_id ) . lean ( ) . exec ( err = > { // delete old notes
2020-05-14 15:36:47 +02:00
if ( err ) return console . error ( err ) ;
} ) ;
2020-05-07 21:55:29 +02:00
}
2020-05-06 14:39:04 +02:00
}
2020-05-14 15:36:47 +02:00
if ( _ . keys ( sample . notes ) . length > 0 && newNotes ) { // save new notes
if ( ! await sampleRefCheck ( sample , res , next ) ) return ;
if ( sample . notes . hasOwnProperty ( 'custom_fields' ) && Object . keys ( sample . notes . custom_fields ) . length > 0 ) { // new custom_fields
customFieldsChange ( Object . keys ( sample . notes . custom_fields ) , 1 ) ;
}
let data = await new NoteModel ( sample . notes ) . save ( ) . catch ( err = > { return next ( err ) } ) ; // save new notes
delete sample . notes ;
sample . note_id = data . _id ;
}
2020-05-07 21:55:29 +02:00
}
2020-05-13 12:06:28 +02:00
// check for changes
if ( ! _ . isEqual ( _ . pick ( IdValidate . stringify ( sampleData ) , _ . keys ( sample ) ) , _ . omit ( sample , [ 'notes' ] ) ) ) {
2020-05-27 14:31:17 +02:00
sample . status = globals . status . new ;
2020-05-13 12:06:28 +02:00
}
2020-05-18 14:47:22 +02:00
2020-05-27 14:31:17 +02:00
await SampleModel . findByIdAndUpdate ( req . params . id , sample , { new : true } ) . lean ( ) . exec ( ( err , data : any ) = > {
2020-05-07 21:55:29 +02:00
if ( err ) return next ( err ) ;
res . json ( SampleValidate . output ( data ) ) ;
} ) ;
2020-05-06 14:39:04 +02:00
2020-05-07 21:55:29 +02:00
} ) ;
} ) ;
router . delete ( '/sample/' + IdValidate . parameter ( ) , ( req , res , next ) = > {
if ( ! req . auth ( res , [ 'write' , 'maintain' , 'dev' , 'admin' ] , 'basic' ) ) return ;
SampleModel . findById ( req . params . id ) . lean ( ) . exec ( async ( err , sampleData : any ) = > { // check if id exists
if ( err ) return next ( err ) ;
if ( ! sampleData ) {
return res . status ( 404 ) . json ( { status : 'Not found' } ) ;
}
2020-05-18 14:47:22 +02:00
2020-05-07 21:55:29 +02:00
// only maintain and admin are allowed to edit other user's data
if ( sampleData . user_id . toString ( ) !== req . authDetails . id && ! req . auth ( res , [ 'maintain' , 'admin' ] , 'basic' ) ) return ;
2020-05-27 14:31:17 +02:00
await SampleModel . findByIdAndUpdate ( req . params . id , { status :globals.status.deleted } ) . lean ( ) . exec ( err = > { // set sample status
2020-05-07 21:55:29 +02:00
if ( err ) return next ( err ) ;
2020-05-28 13:05:00 +02:00
// set status of associated measurements also to deleted
2020-05-29 11:06:39 +02:00
MeasurementModel . updateMany ( { sample_id : mongoose.Types.ObjectId ( req . params . id ) } , { status : - 1 } ) . lean ( ) . exec ( err = > {
2020-05-28 13:05:00 +02:00
if ( err ) return next ( err ) ;
if ( sampleData . note_id !== null ) { // handle notes
NoteModel . findById ( sampleData . note_id ) . lean ( ) . exec ( ( err , data : any ) = > { // find notes to update note_fields
if ( err ) return next ( err ) ;
if ( data . hasOwnProperty ( 'custom_fields' ) ) { // update note_fields
customFieldsChange ( Object . keys ( data . custom_fields ) , - 1 ) ;
}
res . json ( { status : 'OK' } ) ;
} ) ;
}
else {
2020-05-07 21:55:29 +02:00
res . json ( { status : 'OK' } ) ;
2020-05-28 13:05:00 +02:00
}
} ) ;
2020-05-07 21:55:29 +02:00
} ) ;
} ) ;
} ) ;
2020-05-06 14:39:04 +02:00
2020-05-28 14:41:35 +02:00
router . put ( '/sample/restore/' + IdValidate . parameter ( ) , ( req , res , next ) = > {
if ( ! req . auth ( res , [ 'maintain' , 'admin' ] , 'basic' ) ) return ;
SampleModel . findByIdAndUpdate ( req . params . id , { status : globals.status.new } ) . lean ( ) . exec ( ( err , data ) = > {
if ( err ) return next ( err ) ;
if ( ! data ) {
return res . status ( 404 ) . json ( { status : 'Not found' } ) ;
}
res . json ( { status : 'OK' } ) ;
} ) ;
} ) ;
2020-05-29 12:22:01 +02:00
router . put ( '/sample/validate/' + IdValidate . parameter ( ) , ( req , res , next ) = > {
if ( ! req . auth ( res , [ 'maintain' , 'admin' ] , 'basic' ) ) return ;
SampleModel . findById ( req . params . id ) . lean ( ) . exec ( ( err , data : any ) = > {
if ( err ) return next ( err ) ;
if ( ! data ) {
return res . status ( 404 ) . json ( { status : 'Not found' } ) ;
}
if ( Object . keys ( data . condition ) . length === 0 ) {
return res . status ( 400 ) . json ( { status : 'Sample without condition cannot be valid' } ) ;
}
MeasurementModel . find ( { sample_id : mongoose.Types.ObjectId ( req . params . id ) } ) . lean ( ) . exec ( ( err , data ) = > {
if ( err ) return next ( err ) ;
if ( data . length === 0 ) {
return res . status ( 400 ) . json ( { status : 'Sample without measurements cannot be valid' } ) ;
}
SampleModel . findByIdAndUpdate ( req . params . id , { status : globals.status.validated } ) . lean ( ) . exec ( err = > {
if ( err ) return next ( err ) ;
res . json ( { status : 'OK' } ) ;
} ) ;
} ) ;
} ) ;
} ) ;
2020-05-07 21:55:29 +02:00
router . post ( '/sample/new' , async ( req , res , next ) = > {
if ( ! req . auth ( res , [ 'write' , 'maintain' , 'dev' , 'admin' ] , 'basic' ) ) return ;
2020-05-06 14:39:04 +02:00
2020-05-27 14:31:17 +02:00
if ( ! req . body . hasOwnProperty ( 'condition' ) ) { // add empty condition if not specified
req . body . condition = { } ;
}
2020-05-07 21:55:29 +02:00
const { error , value : sample } = SampleValidate . input ( req . body , 'new' ) ;
if ( error ) return res400 ( error , res ) ;
if ( ! await materialCheck ( sample , res , next ) ) return ;
if ( ! await sampleRefCheck ( sample , res , next ) ) return ;
if ( sample . notes . hasOwnProperty ( 'custom_fields' ) && Object . keys ( sample . notes . custom_fields ) . length > 0 ) { // new custom_fields
customFieldsChange ( Object . keys ( sample . notes . custom_fields ) , 1 ) ;
}
2020-05-27 14:31:17 +02:00
if ( ! _ . isEmpty ( sample . condition ) ) { // do not execute check if condition is empty
if ( ! await conditionCheck ( sample . condition , 'change' , res , next ) ) return ;
}
sample . status = globals . status . new ; // set status to new
2020-05-18 09:58:15 +02:00
sample . number = await numberGenerate ( sample , req , res , next ) ;
if ( ! sample . number ) return ;
2020-05-18 14:47:22 +02:00
await new NoteModel ( sample . notes ) . save ( ( err , data ) = > { // save notes
2020-05-07 21:55:29 +02:00
if ( err ) return next ( err ) ;
delete sample . notes ;
sample . note_id = data . _id ;
sample . user_id = req . authDetails . id ;
2020-05-27 14:31:17 +02:00
2020-05-07 21:55:29 +02:00
new SampleModel ( sample ) . save ( ( err , data ) = > {
if ( err ) return next ( err ) ;
res . json ( SampleValidate . output ( data . toObject ( ) ) ) ;
2020-05-06 14:39:04 +02:00
} ) ;
2020-05-07 21:55:29 +02:00
} ) ;
2020-05-06 14:39:04 +02:00
} ) ;
router . get ( '/sample/notes/fields' , ( req , res , next ) = > {
if ( ! req . auth ( res , [ 'read' , 'write' , 'maintain' , 'dev' , 'admin' ] , 'all' ) ) return ;
NoteFieldModel . find ( { } ) . lean ( ) . exec ( ( err , data ) = > {
if ( err ) return next ( err ) ;
2020-05-12 17:15:36 +02:00
res . json ( _ . compact ( data . map ( e = > NoteFieldValidate . output ( e ) ) ) ) ; // validate all and filter null values from validation errors
2020-05-06 14:39:04 +02:00
} )
} ) ;
module . exports = router ;
2020-05-27 14:31:17 +02:00
async function numberGenerate ( sample , req , res , next ) { // generate number in format Location32, returns false on error
2020-05-18 09:58:15 +02:00
const sampleData = await SampleModel
2020-05-27 14:31:17 +02:00
. findOne ( { number : new RegExp ( '^' + req . authDetails . location + '[0-9]+$' , 'm' ) } )
. sort ( { number : - 1 } )
2020-05-18 09:58:15 +02:00
. lean ( )
. exec ( )
. catch ( err = > next ( err ) ) ;
if ( sampleData instanceof Error ) return false ;
2020-05-27 14:31:17 +02:00
return req . authDetails . location + ( sampleData ? Number ( sampleData . number . replace ( /[^0-9]+/g , '' ) ) + 1 : 1 ) ;
2020-05-07 21:55:29 +02:00
}
async function materialCheck ( sample , res , next , id = sample . material_id ) { // validate material_id and color, returns false if invalid
2020-05-18 09:58:15 +02:00
const materialData = await MaterialModel . findById ( id ) . lean ( ) . exec ( ) . catch ( err = > next ( err ) ) as any ;
2020-05-14 15:36:47 +02:00
if ( materialData instanceof Error ) return false ;
2020-05-07 21:55:29 +02:00
if ( ! materialData ) { // could not find material_id
res . status ( 400 ) . json ( { status : 'Material not available' } ) ;
return false ;
}
if ( sample . hasOwnProperty ( 'color' ) && ! materialData . numbers . find ( e = > e . color === sample . color ) ) { // color for material not specified
res . status ( 400 ) . json ( { status : 'Color not available for material' } ) ;
return false ;
}
return true ;
}
2020-05-27 14:31:17 +02:00
async function conditionCheck ( condition , param , res , next ) { // validate treatment template, returns false if invalid, otherwise template data
if ( ! condition . condition_template || ! IdValidate . valid ( condition . condition_template ) ) { // template id not found
res . status ( 400 ) . json ( { status : 'Condition template not available' } ) ;
return false ;
}
const conditionData = await ConditionTemplateModel . findById ( condition . condition_template ) . lean ( ) . exec ( ) . catch ( err = > next ( err ) ) as any ;
if ( conditionData instanceof Error ) return false ;
if ( ! conditionData ) { // template not found
res . status ( 400 ) . json ( { status : 'Condition template not available' } ) ;
return false ;
}
// validate parameters
const { error , value : ignore } = ParametersValidate . input ( _ . omit ( condition , 'condition_template' ) , conditionData . parameters , param ) ;
if ( error ) { res400 ( error , res ) ; return false ; }
return conditionData ;
}
2020-05-07 21:55:29 +02:00
function sampleRefCheck ( sample , res , next ) { // validate sample_references, resolves false for invalid reference
return new Promise ( resolve = > {
if ( sample . notes . sample_references . length > 0 ) { // there are sample_references
2020-05-18 14:47:22 +02:00
let referencesCount = sample . notes . sample_references . length ; // count to keep track of running async operations
2020-05-07 21:55:29 +02:00
sample . notes . sample_references . forEach ( reference = > {
2020-05-27 14:31:17 +02:00
SampleModel . findById ( reference . sample_id ) . lean ( ) . exec ( ( err , data ) = > {
2020-05-07 21:55:29 +02:00
if ( err ) { next ( err ) ; resolve ( false ) }
if ( ! data ) {
res . status ( 400 ) . json ( { status : 'Sample reference not available' } ) ;
return resolve ( false ) ;
}
referencesCount -- ;
2020-05-18 14:47:22 +02:00
if ( referencesCount <= 0 ) { // all async requests done
2020-05-07 21:55:29 +02:00
resolve ( true ) ;
}
} ) ;
} ) ;
}
else {
resolve ( true ) ;
}
} ) ;
}
2020-05-18 14:47:22 +02:00
function customFieldsChange ( fields , amount ) { // update custom_fields and respective quantities
2020-05-06 14:39:04 +02:00
fields . forEach ( field = > {
2020-05-07 21:55:29 +02:00
NoteFieldModel . findOneAndUpdate ( { name : field } , { $inc : { qty : amount } } , { new : true } ) . lean ( ) . exec ( ( err , data : any ) = > { // check if field exists
2020-05-06 14:39:04 +02:00
if ( err ) return console . error ( err ) ;
if ( ! data ) { // new field
new NoteFieldModel ( { name : field , qty : 1 } ) . save ( err = > {
if ( err ) return console . error ( err ) ;
} )
}
2020-05-27 14:31:17 +02:00
else if ( data . qty <= 0 ) { // delete document if field is not used anymore
2020-05-07 21:55:29 +02:00
NoteFieldModel . findOneAndDelete ( { name : field } ) . lean ( ) . exec ( err = > {
if ( err ) return console . error ( err ) ;
} ) ;
}
2020-05-06 14:39:04 +02:00
} ) ;
} ) ;
}