Merge pull request #13 in ~VLE2FE/dfop-api from decisions-200525 to develop
* commit '99be1798d0a3c90d297df210e9f15fd877b4508f':
  cleaned TODOS
  implemented changelog
  only allowed latest template version and allowed admin to set sample number
  introduced first_id to reference new template versions to original
  forbid condition_template as parameter name for template
  validation for material
  validation for sample
  validation for measurement
  made GET /sample/{id} work with new material model
  separated groups and suppliers for material PUT and POST
  separated groups and suppliers for material GET
			
			
This commit is contained in:
		
							
								
								
									
										1
									
								
								.idea/dictionaries/VLE2FE.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/dictionaries/VLE2FE.xml
									
									
									
										generated
									
									
									
								
							@@ -5,6 +5,7 @@
 | 
				
			|||||||
      <w>cfenv</w>
 | 
					      <w>cfenv</w>
 | 
				
			||||||
      <w>dfopdb</w>
 | 
					      <w>dfopdb</w>
 | 
				
			||||||
      <w>janedoe</w>
 | 
					      <w>janedoe</w>
 | 
				
			||||||
 | 
					      <w>pagesize</w>
 | 
				
			||||||
      <w>testcomment</w>
 | 
					      <w>testcomment</w>
 | 
				
			||||||
    </words>
 | 
					    </words>
 | 
				
			||||||
  </dictionary>
 | 
					  </dictionary>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@ tags:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
paths:
 | 
					paths:
 | 
				
			||||||
  allOf:
 | 
					  allOf:
 | 
				
			||||||
    - $ref: 'others.yaml'
 | 
					    - $ref: 'root.yaml'
 | 
				
			||||||
    - $ref: 'sample.yaml'
 | 
					    - $ref: 'sample.yaml'
 | 
				
			||||||
    - $ref: 'material.yaml'
 | 
					    - $ref: 'material.yaml'
 | 
				
			||||||
    - $ref: 'measurement.yaml'
 | 
					    - $ref: 'measurement.yaml'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,9 +19,9 @@
 | 
				
			|||||||
      500:
 | 
					      500:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/materials/{group}:
 | 
					/materials/{state}:
 | 
				
			||||||
  parameters:
 | 
					  parameters:
 | 
				
			||||||
    - $ref: 'api.yaml#/components/parameters/Group'
 | 
					    - $ref: 'api.yaml#/components/parameters/State'
 | 
				
			||||||
  get:
 | 
					  get:
 | 
				
			||||||
    summary: lists all new/deleted materials
 | 
					    summary: lists all new/deleted materials
 | 
				
			||||||
    description: 'Auth: basic, levels: maintain, admin'
 | 
					    description: 'Auth: basic, levels: maintain, admin'
 | 
				
			||||||
@@ -140,6 +140,29 @@
 | 
				
			|||||||
      500:
 | 
					      500:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/material/validate/{id}:
 | 
				
			||||||
 | 
					  parameters:
 | 
				
			||||||
 | 
					    - $ref: 'api.yaml#/components/parameters/Id'
 | 
				
			||||||
 | 
					  put:
 | 
				
			||||||
 | 
					    summary: restore material
 | 
				
			||||||
 | 
					    description: 'Auth: basic, levels: maintain, admin'
 | 
				
			||||||
 | 
					    x-doc: status is set to 10
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /material
 | 
				
			||||||
 | 
					    security:
 | 
				
			||||||
 | 
					      - BasicAuth: []
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/Ok'
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      403:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/403'
 | 
				
			||||||
 | 
					      404:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/404'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/material/new:
 | 
					/material/new:
 | 
				
			||||||
  post:
 | 
					  post:
 | 
				
			||||||
    summary: add material
 | 
					    summary: add material
 | 
				
			||||||
@@ -170,3 +193,49 @@
 | 
				
			|||||||
        $ref: 'api.yaml#/components/responses/403'
 | 
					        $ref: 'api.yaml#/components/responses/403'
 | 
				
			||||||
      500:
 | 
					      500:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/material/groups:
 | 
				
			||||||
 | 
					  get:
 | 
				
			||||||
 | 
					    summary: list all existing material groups
 | 
				
			||||||
 | 
					    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /material
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        description: all material groups
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              type: array
 | 
				
			||||||
 | 
					              items:
 | 
				
			||||||
 | 
					                type: string
 | 
				
			||||||
 | 
					                example: PA66
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      403:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/403'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/material/suppliers:
 | 
				
			||||||
 | 
					  get:
 | 
				
			||||||
 | 
					    summary: list all existing material suppliers
 | 
				
			||||||
 | 
					    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /material
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        description: all material suppliers
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              type: array
 | 
				
			||||||
 | 
					              items:
 | 
				
			||||||
 | 
					                type: string
 | 
				
			||||||
 | 
					                example: BASF
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      403:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/403'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
@@ -100,6 +100,29 @@
 | 
				
			|||||||
      500:
 | 
					      500:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/measurement/validate/{id}:
 | 
				
			||||||
 | 
					  parameters:
 | 
				
			||||||
 | 
					    - $ref: 'api.yaml#/components/parameters/Id'
 | 
				
			||||||
 | 
					  put:
 | 
				
			||||||
 | 
					    summary: set measurement status to validated
 | 
				
			||||||
 | 
					    description: 'Auth: basic, levels: maintain, admin'
 | 
				
			||||||
 | 
					    x-doc: status is set to 10
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /measurement
 | 
				
			||||||
 | 
					    security:
 | 
				
			||||||
 | 
					      - BasicAuth: []
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/Ok'
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      403:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/403'
 | 
				
			||||||
 | 
					      404:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/404'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/measurement/new:
 | 
					/measurement/new:
 | 
				
			||||||
  post:
 | 
					  post:
 | 
				
			||||||
    summary: add measurement
 | 
					    summary: add measurement
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,43 +0,0 @@
 | 
				
			|||||||
/:
 | 
					 | 
				
			||||||
  get:
 | 
					 | 
				
			||||||
    summary: Root method
 | 
					 | 
				
			||||||
    description: 'Auth: none'
 | 
					 | 
				
			||||||
    tags:
 | 
					 | 
				
			||||||
      - /
 | 
					 | 
				
			||||||
    security: []
 | 
					 | 
				
			||||||
    responses:
 | 
					 | 
				
			||||||
      200:
 | 
					 | 
				
			||||||
        description: Server is working
 | 
					 | 
				
			||||||
        content:
 | 
					 | 
				
			||||||
          application/json:
 | 
					 | 
				
			||||||
            schema:
 | 
					 | 
				
			||||||
              properties:
 | 
					 | 
				
			||||||
                status:
 | 
					 | 
				
			||||||
                  type: string
 | 
					 | 
				
			||||||
                  example: 'API server up and running!'
 | 
					 | 
				
			||||||
      500:
 | 
					 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/authorized:
 | 
					 | 
				
			||||||
  get:
 | 
					 | 
				
			||||||
    summary: Checks authorization
 | 
					 | 
				
			||||||
    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
					 | 
				
			||||||
    tags:
 | 
					 | 
				
			||||||
      - /
 | 
					 | 
				
			||||||
    responses:
 | 
					 | 
				
			||||||
      200:
 | 
					 | 
				
			||||||
        description: Authorized
 | 
					 | 
				
			||||||
        content:
 | 
					 | 
				
			||||||
          application/json:
 | 
					 | 
				
			||||||
            schema:
 | 
					 | 
				
			||||||
              properties:
 | 
					 | 
				
			||||||
                status:
 | 
					 | 
				
			||||||
                  type: string
 | 
					 | 
				
			||||||
                  example: 'Authorization successful'
 | 
					 | 
				
			||||||
                method:
 | 
					 | 
				
			||||||
                  type: string
 | 
					 | 
				
			||||||
                  example: 'basic'
 | 
					 | 
				
			||||||
      401:
 | 
					 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/401'
 | 
					 | 
				
			||||||
      500:
 | 
					 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					 | 
				
			||||||
@@ -14,7 +14,7 @@ Name:
 | 
				
			|||||||
  schema:
 | 
					  schema:
 | 
				
			||||||
    type: string
 | 
					    type: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Group:
 | 
					State:
 | 
				
			||||||
  name: group
 | 
					  name: group
 | 
				
			||||||
  description: 'possible values: new, deleted'
 | 
					  description: 'possible values: new, deleted'
 | 
				
			||||||
  in: path
 | 
					  in: path
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										102
									
								
								api/root.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								api/root.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					/:
 | 
				
			||||||
 | 
					  get:
 | 
				
			||||||
 | 
					    summary: Root method
 | 
				
			||||||
 | 
					    description: 'Auth: none'
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /
 | 
				
			||||||
 | 
					    security: []
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        description: Server is working
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              properties:
 | 
				
			||||||
 | 
					                status:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  example: 'API server up and running!'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/authorized:
 | 
				
			||||||
 | 
					  get:
 | 
				
			||||||
 | 
					    summary: Checks authorization
 | 
				
			||||||
 | 
					    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        description: Authorized
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              properties:
 | 
				
			||||||
 | 
					                status:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  example: 'Authorization successful'
 | 
				
			||||||
 | 
					                method:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  example: 'basic'
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/changelog/{timestamp}/{page}/{pagesize}:
 | 
				
			||||||
 | 
					  parameters:
 | 
				
			||||||
 | 
					    - name: timestamp
 | 
				
			||||||
 | 
					      in: path
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					      schema:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      example: 1970-01-01T00:00:00.000Z
 | 
				
			||||||
 | 
					    - name: page
 | 
				
			||||||
 | 
					      in: path
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					      schema:
 | 
				
			||||||
 | 
					        type: number
 | 
				
			||||||
 | 
					      example: 3
 | 
				
			||||||
 | 
					    - name: pagesize
 | 
				
			||||||
 | 
					      in: path
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					      schema:
 | 
				
			||||||
 | 
					        type: number
 | 
				
			||||||
 | 
					      example: 30
 | 
				
			||||||
 | 
					  get:
 | 
				
			||||||
 | 
					    summary: get changelog
 | 
				
			||||||
 | 
					    description: 'Auth: basic, levels: maintain, admin<br>Displays all logs older than timestamp, sorted by date descending, page defaults to 0, pagesize defaults to 25<br>Avoid using high page numbers for older logs, better use an older timestamp'
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        description: Changelog
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              properties:
 | 
				
			||||||
 | 
					                date:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  example: 1970-01-01T00:00:00.000Z
 | 
				
			||||||
 | 
					                action:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  example: PUT /sample/400000000000000000000001
 | 
				
			||||||
 | 
					                collection:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  example: samples
 | 
				
			||||||
 | 
					                conditions:
 | 
				
			||||||
 | 
					                  type: object
 | 
				
			||||||
 | 
					                  example:
 | 
				
			||||||
 | 
					                    _id: '400000000000000000000001'
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  type: object
 | 
				
			||||||
 | 
					                  example:
 | 
				
			||||||
 | 
					                    type: part
 | 
				
			||||||
 | 
					                    status: 0
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      403:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/403'
 | 
				
			||||||
 | 
					      404:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/404'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
@@ -19,9 +19,9 @@
 | 
				
			|||||||
      500:
 | 
					      500:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/samples/{group}:
 | 
					/samples/{state}:
 | 
				
			||||||
  parameters:
 | 
					  parameters:
 | 
				
			||||||
    - $ref: 'api.yaml#/components/parameters/Group'
 | 
					    - $ref: 'api.yaml#/components/parameters/State'
 | 
				
			||||||
  get:
 | 
					  get:
 | 
				
			||||||
    summary: all new/deleted samples in overview
 | 
					    summary: all new/deleted samples in overview
 | 
				
			||||||
    description: 'Auth: basic, levels: maintain, admin'
 | 
					    description: 'Auth: basic, levels: maintain, admin'
 | 
				
			||||||
@@ -142,10 +142,35 @@
 | 
				
			|||||||
      500:
 | 
					      500:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/sample/validate/{id}:
 | 
				
			||||||
 | 
					  parameters:
 | 
				
			||||||
 | 
					    - $ref: 'api.yaml#/components/parameters/Id'
 | 
				
			||||||
 | 
					  put:
 | 
				
			||||||
 | 
					    summary: set sample status to validated
 | 
				
			||||||
 | 
					    description: 'Auth: basic, levels: maintain, admin'
 | 
				
			||||||
 | 
					    x-doc: status is set to 10
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /sample
 | 
				
			||||||
 | 
					    security:
 | 
				
			||||||
 | 
					      - BasicAuth: []
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/Ok'
 | 
				
			||||||
 | 
					      400:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/400'
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      403:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/403'
 | 
				
			||||||
 | 
					      404:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/404'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/sample/new:
 | 
					/sample/new:
 | 
				
			||||||
  post:
 | 
					  post:
 | 
				
			||||||
    summary: add sample
 | 
					    summary: add sample
 | 
				
			||||||
    description: 'Auth: basic, levels: write, maintain, dev, admin'
 | 
					    description: 'Auth: basic, levels: write, maintain, dev, admin.   Number property is only for admin when adding existing samples'
 | 
				
			||||||
    x-doc: 'Adds status: 0 automatically'
 | 
					    x-doc: 'Adds status: 0 automatically'
 | 
				
			||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - /sample
 | 
					      - /sample
 | 
				
			||||||
@@ -156,7 +181,12 @@
 | 
				
			|||||||
      content:
 | 
					      content:
 | 
				
			||||||
        application/json:
 | 
					        application/json:
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: 'api.yaml#/components/schemas/Sample'
 | 
					            allOf:
 | 
				
			||||||
 | 
					              - $ref: 'api.yaml#/components/schemas/Sample'
 | 
				
			||||||
 | 
					            properties:
 | 
				
			||||||
 | 
					              number:
 | 
				
			||||||
 | 
					                type: string
 | 
				
			||||||
 | 
					                readOnly: false
 | 
				
			||||||
    responses:
 | 
					    responses:
 | 
				
			||||||
      200:
 | 
					      200:
 | 
				
			||||||
        description: samples details
 | 
					        description: samples details
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,6 +69,7 @@ Sample:
 | 
				
			|||||||
              relation:
 | 
					              relation:
 | 
				
			||||||
                type: string
 | 
					                type: string
 | 
				
			||||||
                example: part to this sample
 | 
					                example: part to this sample
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SampleDetail:
 | 
					SampleDetail:
 | 
				
			||||||
  allOf:
 | 
					  allOf:
 | 
				
			||||||
    - $ref: 'api.yaml#/components/schemas/_Id'
 | 
					    - $ref: 'api.yaml#/components/schemas/_Id'
 | 
				
			||||||
@@ -165,14 +166,6 @@ Template:
 | 
				
			|||||||
              min: 0
 | 
					              min: 0
 | 
				
			||||||
              max: 2
 | 
					              max: 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ConditionTemplate:
 | 
					 | 
				
			||||||
  allOf:
 | 
					 | 
				
			||||||
    - $ref: 'api.yaml#/components/schemas/Template'
 | 
					 | 
				
			||||||
  properties:
 | 
					 | 
				
			||||||
    number_prefix:
 | 
					 | 
				
			||||||
      type: string
 | 
					 | 
				
			||||||
      example: B
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Email:
 | 
					Email:
 | 
				
			||||||
  properties:
 | 
					  properties:
 | 
				
			||||||
    email:
 | 
					    email:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@
 | 
				
			|||||||
            schema:
 | 
					            schema:
 | 
				
			||||||
              type: array
 | 
					              type: array
 | 
				
			||||||
              items:
 | 
					              items:
 | 
				
			||||||
                $ref: 'api.yaml#/components/schemas/ConditionTemplate'
 | 
					                $ref: 'api.yaml#/components/schemas/Template'
 | 
				
			||||||
      401:
 | 
					      401:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/401'
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
      500:
 | 
					      500:
 | 
				
			||||||
@@ -36,7 +36,7 @@
 | 
				
			|||||||
        content:
 | 
					        content:
 | 
				
			||||||
          application/json:
 | 
					          application/json:
 | 
				
			||||||
            schema:
 | 
					            schema:
 | 
				
			||||||
              $ref: 'api.yaml#/components/schemas/ConditionTemplate'
 | 
					              $ref: 'api.yaml#/components/schemas/Template'
 | 
				
			||||||
      401:
 | 
					      401:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/401'
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
      404:
 | 
					      404:
 | 
				
			||||||
@@ -56,14 +56,14 @@
 | 
				
			|||||||
      content:
 | 
					      content:
 | 
				
			||||||
        application/json:
 | 
					        application/json:
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: 'api.yaml#/components/schemas/ConditionTemplate'
 | 
					            $ref: 'api.yaml#/components/schemas/Template'
 | 
				
			||||||
    responses:
 | 
					    responses:
 | 
				
			||||||
      200:
 | 
					      200:
 | 
				
			||||||
        description: condition details
 | 
					        description: condition details
 | 
				
			||||||
        content:
 | 
					        content:
 | 
				
			||||||
          application/json:
 | 
					          application/json:
 | 
				
			||||||
            schema:
 | 
					            schema:
 | 
				
			||||||
              $ref: 'api.yaml#/components/schemas/ConditionTemplate'
 | 
					              $ref: 'api.yaml#/components/schemas/Template'
 | 
				
			||||||
      400:
 | 
					      400:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/400'
 | 
					        $ref: 'api.yaml#/components/responses/400'
 | 
				
			||||||
      401:
 | 
					      401:
 | 
				
			||||||
@@ -88,14 +88,14 @@
 | 
				
			|||||||
      content:
 | 
					      content:
 | 
				
			||||||
        application/json:
 | 
					        application/json:
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: 'api.yaml#/components/schemas/ConditionTemplate'
 | 
					            $ref: 'api.yaml#/components/schemas/Template'
 | 
				
			||||||
    responses:
 | 
					    responses:
 | 
				
			||||||
      200:
 | 
					      200:
 | 
				
			||||||
        description: condition details
 | 
					        description: condition details
 | 
				
			||||||
        content:
 | 
					        content:
 | 
				
			||||||
          application/json:
 | 
					          application/json:
 | 
				
			||||||
            schema:
 | 
					            schema:
 | 
				
			||||||
              $ref: 'api.yaml#/components/schemas/ConditionTemplate'
 | 
					              $ref: 'api.yaml#/components/schemas/Template'
 | 
				
			||||||
      400:
 | 
					      400:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/400'
 | 
					        $ref: 'api.yaml#/components/responses/400'
 | 
				
			||||||
      401:
 | 
					      401:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								build.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								build.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					call npm run tsc-full
 | 
				
			||||||
 | 
					copy package.json dist\package.json
 | 
				
			||||||
 | 
					Xcopy /E /I api dist\api
 | 
				
			||||||
 | 
					Xcopy /E /I static dist\static
 | 
				
			||||||
							
								
								
									
										0
									
								
								data_import/import.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								data_import/import.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										169
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										169
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -678,6 +678,11 @@
 | 
				
			|||||||
        "type-is": "~1.6.17"
 | 
					        "type-is": "~1.6.17"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "bowser": {
 | 
				
			||||||
 | 
					      "version": "2.9.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "boxen": {
 | 
					    "boxen": {
 | 
				
			||||||
      "version": "4.2.0",
 | 
					      "version": "4.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
 | 
				
			||||||
@@ -861,6 +866,11 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
 | 
					      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "camelize": {
 | 
				
			||||||
 | 
					      "version": "1.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "cfenv": {
 | 
					    "cfenv": {
 | 
				
			||||||
      "version": "1.2.2",
 | 
					      "version": "1.2.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/cfenv/-/cfenv-1.2.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/cfenv/-/cfenv-1.2.2.tgz",
 | 
				
			||||||
@@ -998,6 +1008,35 @@
 | 
				
			|||||||
      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
 | 
					      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "compressible": {
 | 
				
			||||||
 | 
					      "version": "2.0.18",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "mime-db": ">= 1.43.0 < 2"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "compression": {
 | 
				
			||||||
 | 
					      "version": "1.7.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "accepts": "~1.3.5",
 | 
				
			||||||
 | 
					        "bytes": "3.0.0",
 | 
				
			||||||
 | 
					        "compressible": "~2.0.16",
 | 
				
			||||||
 | 
					        "debug": "2.6.9",
 | 
				
			||||||
 | 
					        "on-headers": "~1.0.2",
 | 
				
			||||||
 | 
					        "safe-buffer": "5.1.2",
 | 
				
			||||||
 | 
					        "vary": "~1.1.2"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "bytes": {
 | 
				
			||||||
 | 
					          "version": "3.0.0",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "concat-map": {
 | 
					    "concat-map": {
 | 
				
			||||||
      "version": "0.0.1",
 | 
					      "version": "0.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
				
			||||||
@@ -1029,6 +1068,11 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/content-filter/-/content-filter-1.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/content-filter/-/content-filter-1.1.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-VaZ4Y7h776r0v2WxWqu3iatjYI6/N0msXK8O1ymtkFWbSvaFoCePksS8U60BS6dUMZeAlqhN09SuM7ghdzRP1Q=="
 | 
					      "integrity": "sha512-VaZ4Y7h776r0v2WxWqu3iatjYI6/N0msXK8O1ymtkFWbSvaFoCePksS8U60BS6dUMZeAlqhN09SuM7ghdzRP1Q=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "content-security-policy-builder": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "content-type": {
 | 
					    "content-type": {
 | 
				
			||||||
      "version": "1.0.4",
 | 
					      "version": "1.0.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
 | 
				
			||||||
@@ -1092,6 +1136,11 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="
 | 
					      "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "dasherize": {
 | 
				
			||||||
 | 
					      "version": "2.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "debug": {
 | 
					    "debug": {
 | 
				
			||||||
      "version": "2.6.9",
 | 
					      "version": "2.6.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
 | 
				
			||||||
@@ -1163,6 +1212,16 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
 | 
					      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "dns-prefetch-control": {
 | 
				
			||||||
 | 
					      "version": "0.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "dont-sniff-mimetype": {
 | 
				
			||||||
 | 
					      "version": "1.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "dot-prop": {
 | 
					    "dot-prop": {
 | 
				
			||||||
      "version": "5.2.0",
 | 
					      "version": "5.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
 | 
				
			||||||
@@ -1265,6 +1324,11 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
 | 
				
			||||||
      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
 | 
					      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "expect-ct": {
 | 
				
			||||||
 | 
					      "version": "0.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "express": {
 | 
					    "express": {
 | 
				
			||||||
      "version": "4.17.1",
 | 
					      "version": "4.17.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
 | 
				
			||||||
@@ -1308,6 +1372,11 @@
 | 
				
			|||||||
      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
 | 
					      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "feature-policy": {
 | 
				
			||||||
 | 
					      "version": "0.3.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "fill-range": {
 | 
					    "fill-range": {
 | 
				
			||||||
      "version": "7.0.1",
 | 
					      "version": "7.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
 | 
				
			||||||
@@ -1409,6 +1478,11 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
 | 
				
			||||||
      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
 | 
					      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "frameguard": {
 | 
				
			||||||
 | 
					      "version": "3.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "fresh": {
 | 
					    "fresh": {
 | 
				
			||||||
      "version": "0.5.2",
 | 
					      "version": "0.5.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
 | 
				
			||||||
@@ -1568,6 +1642,76 @@
 | 
				
			|||||||
      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
 | 
					      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "helmet": {
 | 
				
			||||||
 | 
					      "version": "3.22.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.22.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Xrqicn2nm1ZIUxP3YGuTBmbDL04neKsIT583Sjh0FkiwKDXYCMUqGqC88w3NUvVXtA75JyR2Jn6jw6ZEMOD+ZA==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "depd": "2.0.0",
 | 
				
			||||||
 | 
					        "dns-prefetch-control": "0.2.0",
 | 
				
			||||||
 | 
					        "dont-sniff-mimetype": "1.1.0",
 | 
				
			||||||
 | 
					        "expect-ct": "0.2.0",
 | 
				
			||||||
 | 
					        "feature-policy": "0.3.0",
 | 
				
			||||||
 | 
					        "frameguard": "3.1.0",
 | 
				
			||||||
 | 
					        "helmet-crossdomain": "0.4.0",
 | 
				
			||||||
 | 
					        "helmet-csp": "2.10.0",
 | 
				
			||||||
 | 
					        "hide-powered-by": "1.1.0",
 | 
				
			||||||
 | 
					        "hpkp": "2.0.0",
 | 
				
			||||||
 | 
					        "hsts": "2.2.0",
 | 
				
			||||||
 | 
					        "ienoopen": "1.1.0",
 | 
				
			||||||
 | 
					        "nocache": "2.1.0",
 | 
				
			||||||
 | 
					        "referrer-policy": "1.2.0",
 | 
				
			||||||
 | 
					        "x-xss-protection": "1.3.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "depd": {
 | 
				
			||||||
 | 
					          "version": "2.0.0",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "helmet-crossdomain": {
 | 
				
			||||||
 | 
					      "version": "0.4.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "helmet-csp": {
 | 
				
			||||||
 | 
					      "version": "2.10.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "bowser": "2.9.0",
 | 
				
			||||||
 | 
					        "camelize": "1.0.0",
 | 
				
			||||||
 | 
					        "content-security-policy-builder": "2.1.0",
 | 
				
			||||||
 | 
					        "dasherize": "2.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "hide-powered-by": {
 | 
				
			||||||
 | 
					      "version": "1.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "hpkp": {
 | 
				
			||||||
 | 
					      "version": "2.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "hsts": {
 | 
				
			||||||
 | 
					      "version": "2.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "depd": "2.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "depd": {
 | 
				
			||||||
 | 
					          "version": "2.0.0",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "html-escaper": {
 | 
					    "html-escaper": {
 | 
				
			||||||
      "version": "2.0.2",
 | 
					      "version": "2.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
 | 
				
			||||||
@@ -1599,6 +1743,11 @@
 | 
				
			|||||||
        "safer-buffer": ">= 2.1.2 < 3"
 | 
					        "safer-buffer": ">= 2.1.2 < 3"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "ienoopen": {
 | 
				
			||||||
 | 
					      "version": "1.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "ignore-by-default": {
 | 
					    "ignore-by-default": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
 | 
				
			||||||
@@ -2270,6 +2419,11 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
 | 
					      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "nocache": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node-environment-flags": {
 | 
					    "node-environment-flags": {
 | 
				
			||||||
      "version": "1.0.6",
 | 
					      "version": "1.0.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
 | 
				
			||||||
@@ -2571,6 +2725,11 @@
 | 
				
			|||||||
        "ee-first": "1.1.1"
 | 
					        "ee-first": "1.1.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "on-headers": {
 | 
				
			||||||
 | 
					      "version": "1.0.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "once": {
 | 
					    "once": {
 | 
				
			||||||
      "version": "1.4.0",
 | 
					      "version": "1.4.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
 | 
				
			||||||
@@ -2845,6 +3004,11 @@
 | 
				
			|||||||
        "picomatch": "^2.0.7"
 | 
					        "picomatch": "^2.0.7"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "referrer-policy": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "regexp-clone": {
 | 
					    "regexp-clone": {
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
 | 
				
			||||||
@@ -3652,6 +3816,11 @@
 | 
				
			|||||||
        "typedarray-to-buffer": "^3.1.5"
 | 
					        "typedarray-to-buffer": "^3.1.5"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "x-xss-protection": {
 | 
				
			||||||
 | 
					      "version": "1.3.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "xdg-basedir": {
 | 
					    "xdg-basedir": {
 | 
				
			||||||
      "version": "4.0.0",
 | 
					      "version": "4.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								package.json
									
									
									
									
									
								
							@@ -6,9 +6,10 @@
 | 
				
			|||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "tsc": "tsc",
 | 
					    "tsc": "tsc",
 | 
				
			||||||
    "tsc-full": "del /q dist\\* & (for /d %x in (dist\\*) do @rd /s /q \"%x\") & tsc",
 | 
					    "tsc-full": "del /q dist\\* & (for /d %x in (dist\\*) do @rd /s /q \"%x\") & tsc",
 | 
				
			||||||
 | 
					    "build": "build.bat",
 | 
				
			||||||
    "test": "mocha dist/**/**.spec.js",
 | 
					    "test": "mocha dist/**/**.spec.js",
 | 
				
			||||||
    "start": "tsc && node dist/index.js || exit 1",
 | 
					    "start": "node index.js",
 | 
				
			||||||
    "dev": "nodemon -e ts,yaml --exec \"npm run start\"",
 | 
					    "dev": "nodemon -e ts,yaml --exec \"tsc && node dist/index.js || exit 1\"",
 | 
				
			||||||
    "loadDev": "node dist/test/loadDev.js",
 | 
					    "loadDev": "node dist/test/loadDev.js",
 | 
				
			||||||
    "coverage": "tsc && nyc --reporter=html --reporter=text mocha dist/**/**.spec.js --timeout 5000"
 | 
					    "coverage": "tsc && nyc --reporter=html --reporter=text mocha dist/**/**.spec.js --timeout 5000"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@@ -19,35 +20,37 @@
 | 
				
			|||||||
    "@apidevtools/json-schema-ref-parser": "^8.0.0",
 | 
					    "@apidevtools/json-schema-ref-parser": "^8.0.0",
 | 
				
			||||||
    "@apidevtools/swagger-parser": "^9.0.1",
 | 
					    "@apidevtools/swagger-parser": "^9.0.1",
 | 
				
			||||||
    "@hapi/joi": "^17.1.1",
 | 
					    "@hapi/joi": "^17.1.1",
 | 
				
			||||||
    "@types/bcrypt": "^3.0.0",
 | 
					 | 
				
			||||||
    "@types/body-parser": "^1.19.0",
 | 
					 | 
				
			||||||
    "@types/express-serve-static-core": "^4.17.5",
 | 
					 | 
				
			||||||
    "@types/mocha": "^5.2.7",
 | 
					 | 
				
			||||||
    "@types/mongoose": "^5.7.12",
 | 
					 | 
				
			||||||
    "@types/node": "^13.1.6",
 | 
					 | 
				
			||||||
    "@types/qs": "^6.9.1",
 | 
					 | 
				
			||||||
    "@types/serve-static": "^1.13.3",
 | 
					 | 
				
			||||||
    "axios": "^0.19.2",
 | 
					    "axios": "^0.19.2",
 | 
				
			||||||
    "basic-auth": "^2.0.1",
 | 
					    "basic-auth": "^2.0.1",
 | 
				
			||||||
    "bcryptjs": "^2.4.3",
 | 
					    "bcryptjs": "^2.4.3",
 | 
				
			||||||
    "body-parser": "^1.19.0",
 | 
					    "body-parser": "^1.19.0",
 | 
				
			||||||
    "cfenv": "^1.2.2",
 | 
					    "cfenv": "^1.2.2",
 | 
				
			||||||
 | 
					    "compression": "^1.7.4",
 | 
				
			||||||
    "content-filter": "^1.1.2",
 | 
					    "content-filter": "^1.1.2",
 | 
				
			||||||
    "express": "^4.17.1",
 | 
					    "express": "^4.17.1",
 | 
				
			||||||
 | 
					    "helmet": "^3.22.0",
 | 
				
			||||||
    "json-schema": "^0.2.5",
 | 
					    "json-schema": "^0.2.5",
 | 
				
			||||||
    "lodash": "^4.17.15",
 | 
					    "lodash": "^4.17.15",
 | 
				
			||||||
    "mongo-sanitize": "^1.1.0",
 | 
					    "mongo-sanitize": "^1.1.0",
 | 
				
			||||||
    "mongoose": "^5.8.7",
 | 
					    "mongoose": "^5.8.7",
 | 
				
			||||||
    "nodemon": "^2.0.3",
 | 
					    "swagger-ui-express": "^4.1.2"
 | 
				
			||||||
    "swagger-ui-express": "^4.1.2",
 | 
					 | 
				
			||||||
    "tslint": "^5.20.1",
 | 
					 | 
				
			||||||
    "typescript": "^3.7.4"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@types/bcrypt": "^3.0.0",
 | 
				
			||||||
 | 
					    "@types/body-parser": "^1.19.0",
 | 
				
			||||||
 | 
					    "@types/express-serve-static-core": "^4.17.5",
 | 
				
			||||||
    "@types/lodash": "^4.14.150",
 | 
					    "@types/lodash": "^4.14.150",
 | 
				
			||||||
 | 
					    "@types/mocha": "^5.2.7",
 | 
				
			||||||
 | 
					    "@types/mongoose": "^5.7.12",
 | 
				
			||||||
 | 
					    "@types/node": "^13.1.6",
 | 
				
			||||||
 | 
					    "@types/qs": "^6.9.1",
 | 
				
			||||||
 | 
					    "@types/serve-static": "^1.13.3",
 | 
				
			||||||
    "mocha": "^7.1.2",
 | 
					    "mocha": "^7.1.2",
 | 
				
			||||||
 | 
					    "nodemon": "^2.0.3",
 | 
				
			||||||
    "nyc": "^15.0.1",
 | 
					    "nyc": "^15.0.1",
 | 
				
			||||||
    "should": "^13.2.3",
 | 
					    "should": "^13.2.3",
 | 
				
			||||||
    "supertest": "^4.0.2"
 | 
					    "supertest": "^4.0.2",
 | 
				
			||||||
 | 
					    "tslint": "^5.20.1",
 | 
				
			||||||
 | 
					    "typescript": "^3.7.4"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ export default class api {
 | 
				
			|||||||
  static setup () {
 | 
					  static setup () {
 | 
				
			||||||
    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.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 = this.resolveXDoc(apiDoc);
 | 
					      apiDoc = this.resolveXDoc(apiDoc);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								src/db.ts
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								src/db.ts
									
									
									
									
									
								
							@@ -1,5 +1,7 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
import cfenv from 'cfenv';
 | 
					import cfenv from 'cfenv';
 | 
				
			||||||
 | 
					import _ from 'lodash';
 | 
				
			||||||
 | 
					import ChangelogModel from './models/changelog';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// mongoose.set('debug', true);  // enable mongoose debug
 | 
					// mongoose.set('debug', true);  // enable mongoose debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -112,6 +114,27 @@ export default class db {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // changelog entry
 | 
				
			||||||
 | 
					  static log(req, thisOrCollection, conditions = null, data = null) {  // expects (req, this (from query helper)) or (req, collection, conditions, data)
 | 
				
			||||||
 | 
					    if (! (conditions || data)) {  // (req, this)
 | 
				
			||||||
 | 
					      data = thisOrCollection._update ? _.cloneDeep(thisOrCollection._update) : {};  // replace undefined with {}
 | 
				
			||||||
 | 
					      Object.keys(data).forEach(key => {
 | 
				
			||||||
 | 
					        if (key[0] === '$') {
 | 
				
			||||||
 | 
					          data[key.substr(1)] = data[key];
 | 
				
			||||||
 | 
					          delete data[key];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      new ChangelogModel({action: req.method + ' ' + req.url, collectionName: thisOrCollection._collection.collectionName, conditions: thisOrCollection._conditions, data: data, user_id: req.authDetails.id ? req.authDetails.id : null}).save(err => {
 | 
				
			||||||
 | 
					        if (err) console.error(err);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {  // (req, collection, conditions, data)
 | 
				
			||||||
 | 
					      new ChangelogModel({action: req.method + ' ' + req.url, collectionName: thisOrCollection, conditions: conditions, data: data, user_id: req.authDetails.id ? req.authDetails.id : null}).save(err => {
 | 
				
			||||||
 | 
					        if (err) console.error(err);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/index.ts
									
									
									
									
									
								
							@@ -1,20 +1,12 @@
 | 
				
			|||||||
import express from 'express';
 | 
					import express from 'express';
 | 
				
			||||||
import bodyParser from 'body-parser';
 | 
					import bodyParser from 'body-parser';
 | 
				
			||||||
 | 
					import compression from 'compression';
 | 
				
			||||||
import contentFilter from 'content-filter';
 | 
					import contentFilter from 'content-filter';
 | 
				
			||||||
import mongoSanitize from 'mongo-sanitize';
 | 
					import mongoSanitize from 'mongo-sanitize';
 | 
				
			||||||
 | 
					import helmet from 'helmet';
 | 
				
			||||||
import api from './api';
 | 
					import api from './api';
 | 
				
			||||||
import db from './db';
 | 
					import db from './db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: changelog
 | 
					 | 
				
			||||||
// TODO: check executing index.js/move everything needed into dist
 | 
					 | 
				
			||||||
// TODO: One condition per sample
 | 
					 | 
				
			||||||
// TODO: validation: VZ, Humidity: min/max value, DPT: filename
 | 
					 | 
				
			||||||
// TODO: condition values not needed on initial add
 | 
					 | 
				
			||||||
// TODO: add multiple samples at once
 | 
					 | 
				
			||||||
// TODO: coverage
 | 
					 | 
				
			||||||
// TODO: think about the display of deleted/new samples and validation in data and UI
 | 
					 | 
				
			||||||
// TODO: improve error coverage
 | 
					 | 
				
			||||||
// TODO: guess properties from material name in UI
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 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' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
 | 
					console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
 | 
				
			||||||
@@ -31,8 +23,10 @@ app.disable('x-powered-by');
 | 
				
			|||||||
const port = process.env.PORT || 3000;
 | 
					const port = process.env.PORT || 3000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//middleware
 | 
					//middleware
 | 
				
			||||||
 | 
					app.use(helmet());
 | 
				
			||||||
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(compression());  // compress responses
 | 
				
			||||||
app.use(bodyParser.json());
 | 
					app.use(bodyParser.json());
 | 
				
			||||||
app.use(contentFilter());  // filter URL query attacks
 | 
					app.use(contentFilter());  // filter URL query attacks
 | 
				
			||||||
app.use((req, res, next) => {  // filter body query attacks
 | 
					app.use((req, res, next) => {  // filter body query attacks
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/models/changelog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/models/changelog.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ChangelogSchema = new mongoose.Schema({
 | 
				
			||||||
 | 
					  action: String,
 | 
				
			||||||
 | 
					  collectionName: String,
 | 
				
			||||||
 | 
					  conditions: Object,
 | 
				
			||||||
 | 
					  data: Object,
 | 
				
			||||||
 | 
					  user_id: mongoose.Schema.Types.ObjectId
 | 
				
			||||||
 | 
					}, {minimize: false});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('changelog', ChangelogSchema);
 | 
				
			||||||
@@ -1,12 +1,20 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ConditionTemplateSchema = new mongoose.Schema({
 | 
					const ConditionTemplateSchema = new mongoose.Schema({
 | 
				
			||||||
 | 
					  first_id: mongoose.Schema.Types.ObjectId,
 | 
				
			||||||
  name: String,
 | 
					  name: String,
 | 
				
			||||||
  version: Number,
 | 
					  version: Number,
 | 
				
			||||||
  parameters: [{
 | 
					  parameters: [new mongoose.Schema({
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
    range: mongoose.Schema.Types.Mixed
 | 
					    range: mongoose.Schema.Types.Mixed
 | 
				
			||||||
  }]
 | 
					  } ,{ _id : false })]
 | 
				
			||||||
}, {minimize: false});  // to allow empty objects
 | 
					}, {minimize: false});  // to allow empty objects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('condition_template', ConditionTemplateSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					ConditionTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('condition_template', ConditionTemplateSchema);
 | 
				
			||||||
@@ -1,12 +1,15 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import MaterialSupplierModel from '../models/material_suppliers';
 | 
				
			||||||
 | 
					import MaterialGroupsModel from '../models/material_groups';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MaterialSchema = new mongoose.Schema({
 | 
					const MaterialSchema = new mongoose.Schema({
 | 
				
			||||||
  name: {type: String, index: {unique: true}},
 | 
					  name: {type: String, index: {unique: true}},
 | 
				
			||||||
  supplier: String,
 | 
					  supplier_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialSupplierModel},
 | 
				
			||||||
  group: String,
 | 
					  group_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialGroupsModel},
 | 
				
			||||||
  mineral: String,
 | 
					  mineral: Number,
 | 
				
			||||||
  glass_fiber: String,
 | 
					  glass_fiber: Number,
 | 
				
			||||||
  carbon_fiber: String,
 | 
					  carbon_fiber: Number,
 | 
				
			||||||
  numbers: [{
 | 
					  numbers: [{
 | 
				
			||||||
    color: String,
 | 
					    color: String,
 | 
				
			||||||
    number: String
 | 
					    number: String
 | 
				
			||||||
@@ -14,4 +17,10 @@ const MaterialSchema = new mongoose.Schema({
 | 
				
			|||||||
  status: Number
 | 
					  status: Number
 | 
				
			||||||
}, {minimize: false});
 | 
					}, {minimize: false});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('material', MaterialSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					MaterialSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('material', MaterialSchema);
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/models/material_groups.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/models/material_groups.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MaterialGroupsSchema = new mongoose.Schema({
 | 
				
			||||||
 | 
					  name: {type: String, index: {unique: true}}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					MaterialGroupsSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('material_groups', MaterialGroupsSchema);
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/models/material_suppliers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/models/material_suppliers.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MaterialSuppliersSchema = new mongoose.Schema({
 | 
				
			||||||
 | 
					  name: {type: String, index: {unique: true}}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					MaterialSuppliersSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('material_suppliers', MaterialSuppliersSchema);
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
import SampleModel from './sample';
 | 
					import SampleModel from './sample';
 | 
				
			||||||
import MeasurementTemplateModel from './measurement_template';
 | 
					import MeasurementTemplateModel from './measurement_template';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,4 +12,10 @@ const MeasurementSchema = new mongoose.Schema({
 | 
				
			|||||||
  status: Number
 | 
					  status: Number
 | 
				
			||||||
}, {minimize: false});
 | 
					}, {minimize: false});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('measurement', MeasurementSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					MeasurementSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('measurement', MeasurementSchema);
 | 
				
			||||||
@@ -1,12 +1,20 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MeasurementTemplateSchema = new mongoose.Schema({
 | 
					const MeasurementTemplateSchema = new mongoose.Schema({
 | 
				
			||||||
 | 
					  first_id: mongoose.Schema.Types.ObjectId,
 | 
				
			||||||
  name: String,
 | 
					  name: String,
 | 
				
			||||||
  version: Number,
 | 
					  version: Number,
 | 
				
			||||||
  parameters: [{
 | 
					  parameters: [new mongoose.Schema({
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
    range: mongoose.Schema.Types.Mixed
 | 
					    range: mongoose.Schema.Types.Mixed
 | 
				
			||||||
  }]
 | 
					  } ,{ _id : false })]
 | 
				
			||||||
}, {minimize: false});  // to allow empty objects
 | 
					}, {minimize: false});  // to allow empty objects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('measurement_template', MeasurementTemplateSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					MeasurementTemplateSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('measurement_template', MeasurementTemplateSchema);
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const NoteSchema = new mongoose.Schema({
 | 
					const NoteSchema = new mongoose.Schema({
 | 
				
			||||||
  comment: String,
 | 
					  comment: String,
 | 
				
			||||||
@@ -9,4 +10,10 @@ const NoteSchema = new mongoose.Schema({
 | 
				
			|||||||
  custom_fields: mongoose.Schema.Types.Mixed
 | 
					  custom_fields: mongoose.Schema.Types.Mixed
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('note', NoteSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					NoteSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('note', NoteSchema);
 | 
				
			||||||
@@ -1,8 +1,15 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const NoteFieldSchema = new mongoose.Schema({
 | 
					const NoteFieldSchema = new mongoose.Schema({
 | 
				
			||||||
  name: {type: String, index: {unique: true}},
 | 
					  name: {type: String, index: {unique: true}},
 | 
				
			||||||
  qty: Number
 | 
					  qty: Number
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('note_field', NoteFieldSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					NoteFieldSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('note_field', NoteFieldSchema);
 | 
				
			||||||
@@ -3,6 +3,7 @@ import mongoose from 'mongoose';
 | 
				
			|||||||
import MaterialModel from './material';
 | 
					import MaterialModel from './material';
 | 
				
			||||||
import NoteModel from './note';
 | 
					import NoteModel from './note';
 | 
				
			||||||
import UserModel from './user';
 | 
					import UserModel from './user';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SampleSchema = new mongoose.Schema({
 | 
					const SampleSchema = new mongoose.Schema({
 | 
				
			||||||
  number: {type: String, index: {unique: true}},
 | 
					  number: {type: String, index: {unique: true}},
 | 
				
			||||||
@@ -16,4 +17,10 @@ const SampleSchema = new mongoose.Schema({
 | 
				
			|||||||
  status: Number
 | 
					  status: Number
 | 
				
			||||||
}, {minimize: false});
 | 
					}, {minimize: false});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('sample', SampleSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					SampleSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('sample', SampleSchema);
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const UserSchema = new mongoose.Schema({
 | 
					const UserSchema = new mongoose.Schema({
 | 
				
			||||||
  name: {type: String, index: {unique: true}},
 | 
					  name: {type: String, index: {unique: true}},
 | 
				
			||||||
@@ -10,4 +11,10 @@ const UserSchema = new mongoose.Schema({
 | 
				
			|||||||
  device_name: String
 | 
					  device_name: String
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default mongoose.model('user', UserSchema);
 | 
					// changelog query helper
 | 
				
			||||||
 | 
					UserSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
				
			||||||
 | 
					  db.log(req, this);
 | 
				
			||||||
 | 
					  return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default mongoose.model<any, mongoose.Model<any, any>>('user', UserSchema);
 | 
				
			||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
import should from 'should/as-function';
 | 
					import should from 'should/as-function';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
import MaterialModel from '../models/material';
 | 
					import MaterialModel from '../models/material';
 | 
				
			||||||
 | 
					import MaterialGroupModel from '../models/material_groups';
 | 
				
			||||||
 | 
					import MaterialSupplierModel from '../models/material_suppliers';
 | 
				
			||||||
import TestHelper from "../test/helper";
 | 
					import TestHelper from "../test/helper";
 | 
				
			||||||
import globals from '../globals';
 | 
					import globals from '../globals';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: color name must be unique to get color number
 | 
					
 | 
				
			||||||
// TODO: separate supplier/ material name into own collections
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('/material', () => {
 | 
					describe('/material', () => {
 | 
				
			||||||
  let server;
 | 
					  let server;
 | 
				
			||||||
@@ -80,7 +81,7 @@ describe('/material', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('GET /materials/{group}', () => {
 | 
					  describe('GET /materials/{state}', () => {
 | 
				
			||||||
    it('returns all new materials', done => {
 | 
					    it('returns all new materials', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'get',
 | 
					        method: 'get',
 | 
				
			||||||
@@ -268,7 +269,17 @@ describe('/material', () => {
 | 
				
			|||||||
        MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
 | 
					        MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
 | 
				
			||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          should(data).have.property('status',globals.status.validated);
 | 
					          should(data).have.property('status',globals.status.validated);
 | 
				
			||||||
          done();
 | 
					          MaterialGroupModel.find({name: 'PA46'}).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					            if (err) return done(err);
 | 
				
			||||||
 | 
					            should(data).have.lengthOf(1);
 | 
				
			||||||
 | 
					            should(data[0]._id.toString()).be.eql('900000000000000000000001');
 | 
				
			||||||
 | 
					            MaterialSupplierModel.find({name: 'DSM'}).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					              if (err) return done(err);
 | 
				
			||||||
 | 
					              should(data).have.lengthOf(1);
 | 
				
			||||||
 | 
					              should(data[0]._id.toString()).be.eql('110000000000000000000001');
 | 
				
			||||||
 | 
					              done();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -296,19 +307,48 @@ describe('/material', () => {
 | 
				
			|||||||
        auth: {basic: 'janedoe'},
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
        httpStatus: 200,
 | 
					        httpStatus: 200,
 | 
				
			||||||
        req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]}
 | 
					        req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]}
 | 
				
			||||||
        ,
 | 
					 | 
				
			||||||
      }).end((err, res) => {
 | 
					      }).end((err, res) => {
 | 
				
			||||||
        if (err) return done(err);
 | 
					        if (err) return done(err);
 | 
				
			||||||
        should(res.body).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]});
 | 
					        should(res.body).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]});
 | 
				
			||||||
        MaterialModel.findById('100000000000000000000001').lean().exec((err, data:any) => {
 | 
					        MaterialModel.findById('100000000000000000000001').lean().exec((err, data:any) => {
 | 
				
			||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          data._id = data._id.toString();
 | 
					          data._id = data._id.toString();
 | 
				
			||||||
 | 
					          data.group_id = data.group_id.toString();
 | 
				
			||||||
 | 
					          data.supplier_id = data.supplier_id.toString();
 | 
				
			||||||
          data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
 | 
					          data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
 | 
				
			||||||
          should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: '0', glass_fiber: '35', carbon_fiber: '0', numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: 0, __v: 0});
 | 
					          should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier_id: '110000000000000000000002', group_id: '900000000000000000000002', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: 0, __v: 0});
 | 
				
			||||||
          done();
 | 
					          MaterialGroupModel.find({name: 'PA6/6T'}).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					            if (err) return done(err);
 | 
				
			||||||
 | 
					            should(data).have.lengthOf(1);
 | 
				
			||||||
 | 
					            should(data[0]._id.toString()).be.eql('900000000000000000000002');
 | 
				
			||||||
 | 
					            MaterialSupplierModel.find({name: 'BASF'}).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					              if (err) return done(err);
 | 
				
			||||||
 | 
					              should(data).have.lengthOf(1);
 | 
				
			||||||
 | 
					              should(data[0]._id.toString()).be.eql('110000000000000000000002');
 | 
				
			||||||
 | 
					              done();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/100000000000000000000001',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'materials',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            group_id: '900000000000000000000002',
 | 
				
			||||||
 | 
					            supplier_id: '110000000000000000000002',
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          dataIgn: ['supplier', 'group']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('accepts a color without number', done => {
 | 
					    it('accepts a color without number', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -437,13 +477,27 @@ describe('/material', () => {
 | 
				
			|||||||
        MaterialModel.findById('100000000000000000000002').lean().exec((err, data: any) => {
 | 
					        MaterialModel.findById('100000000000000000000002').lean().exec((err, data: any) => {
 | 
				
			||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          data._id = data._id.toString();
 | 
					          data._id = data._id.toString();
 | 
				
			||||||
 | 
					          data.group_id = data.group_id.toString();
 | 
				
			||||||
 | 
					          data.supplier_id = data.supplier_id.toString();
 | 
				
			||||||
          data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
 | 
					          data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
 | 
				
			||||||
          should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: -1, __v: 0}
 | 
					          should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier_id: '110000000000000000000002', group_id: '900000000000000000000002', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: -1, __v: 0}
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          done();
 | 
					          done();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'delete',
 | 
				
			||||||
 | 
					        url: '/material/100000000000000000000002',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'materials',
 | 
				
			||||||
 | 
					          dataAdd: { status: -1}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects deleting a material referenced by samples', done => {
 | 
					    it('rejects deleting a material referenced by samples', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'delete',
 | 
					        method: 'delete',
 | 
				
			||||||
@@ -512,6 +566,21 @@ describe('/material', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/restore/100000000000000000000008',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'materials',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an API key', done => {
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -549,6 +618,76 @@ describe('/material', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('PUT /material/validate/{id}', () => {
 | 
				
			||||||
 | 
					    it('sets the status', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/validate/100000000000000000000007',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done (err);
 | 
				
			||||||
 | 
					        should(res.body).be.eql({status: 'OK'});
 | 
				
			||||||
 | 
					        MaterialModel.findById('100000000000000000000007').lean().exec((err, data: any) => {
 | 
				
			||||||
 | 
					          if (err) return done(err);
 | 
				
			||||||
 | 
					          should(data).have.property('status',globals.status.validated);
 | 
				
			||||||
 | 
					          done();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/validate/100000000000000000000007',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'materials',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            status: 10
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/validate/100000000000000000000007',
 | 
				
			||||||
 | 
					        auth: {key: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 401,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects a write user', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/validate/100000000000000000000007',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 403,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('returns 404 for an unknown sample', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/validate/000000000000000000000007',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 404,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects unauthorized requests', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/material/validate/100000000000000000000007',
 | 
				
			||||||
 | 
					        httpStatus: 401,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('POST /material/new', () => {
 | 
					  describe('POST /material/new', () => {
 | 
				
			||||||
    it('returns the right material', done => {
 | 
					    it('returns the right material', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
@@ -584,23 +723,42 @@ describe('/material', () => {
 | 
				
			|||||||
        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
 | 
					        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
 | 
				
			||||||
      }).end(err => {
 | 
					      }).end(err => {
 | 
				
			||||||
        if (err) return done (err);
 | 
					        if (err) return done (err);
 | 
				
			||||||
        MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
 | 
					        MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, materialData: any) => {
 | 
				
			||||||
          if (err) return done (err);
 | 
					          if (err) return done (err);
 | 
				
			||||||
          should(data).have.lengthOf(1);
 | 
					          should(materialData).have.lengthOf(1);
 | 
				
			||||||
          should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v');
 | 
					          should(materialData[0]).have.only.keys('_id', 'name', 'supplier_id', 'group_id', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v');
 | 
				
			||||||
          should(data[0]).have.property('_id');
 | 
					          should(materialData[0]).have.property('name', 'Crastin CE 2510');
 | 
				
			||||||
          should(data[0]).have.property('name', 'Crastin CE 2510');
 | 
					          should(materialData[0]).have.property('mineral', 0);
 | 
				
			||||||
          should(data[0]).have.property('supplier', 'Du Pont');
 | 
					          should(materialData[0]).have.property('glass_fiber', 30);
 | 
				
			||||||
          should(data[0]).have.property('group', 'PBT');
 | 
					          should(materialData[0]).have.property('carbon_fiber', 0);
 | 
				
			||||||
          should(data[0]).have.property('mineral', '0');
 | 
					          should(materialData[0]).have.property('status',globals.status.new);
 | 
				
			||||||
          should(data[0]).have.property('glass_fiber', '30');
 | 
					          should(materialData[0].numbers).have.lengthOf(0);
 | 
				
			||||||
          should(data[0]).have.property('carbon_fiber', '0');
 | 
					          MaterialGroupModel.findById(materialData[0].group_id).lean().exec((err, data) => {
 | 
				
			||||||
          should(data[0]).have.property('status',globals.status.new);
 | 
					            if (err) return done(err);
 | 
				
			||||||
          should(data[0].numbers).have.lengthOf(0);
 | 
					            should(data).have.property('name', 'PBT')
 | 
				
			||||||
          done();
 | 
					            MaterialSupplierModel.findById(materialData[0].supplier_id).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					              if (err) return done(err);
 | 
				
			||||||
 | 
					              should(data).have.property('name', 'Du Pont');
 | 
				
			||||||
 | 
					              done();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/material/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'materials',
 | 
				
			||||||
 | 
					          dataAdd: {status: 0},
 | 
				
			||||||
 | 
					          dataIgn: ['group_id', 'supplier_id', 'group', 'supplier']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('accepts a color without number', done => {
 | 
					    it('accepts a color without number', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
@@ -626,14 +784,12 @@ describe('/material', () => {
 | 
				
			|||||||
        MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
 | 
					        MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
 | 
				
			||||||
          if (err) return done (err);
 | 
					          if (err) return done (err);
 | 
				
			||||||
          should(data).have.lengthOf(1);
 | 
					          should(data).have.lengthOf(1);
 | 
				
			||||||
          should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v');
 | 
					          should(data[0]).have.only.keys('_id', 'name', 'supplier_id', 'group_id', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v');
 | 
				
			||||||
          should(data[0]).have.property('_id');
 | 
					          should(data[0]).have.property('_id');
 | 
				
			||||||
          should(data[0]).have.property('name', 'Crastin CE 2510');
 | 
					          should(data[0]).have.property('name', 'Crastin CE 2510');
 | 
				
			||||||
          should(data[0]).have.property('supplier', 'Du Pont');
 | 
					          should(data[0]).have.property('mineral', 0);
 | 
				
			||||||
          should(data[0]).have.property('group', 'PBT');
 | 
					          should(data[0]).have.property('glass_fiber', 30);
 | 
				
			||||||
          should(data[0]).have.property('mineral', '0');
 | 
					          should(data[0]).have.property('carbon_fiber', 0);
 | 
				
			||||||
          should(data[0]).have.property('glass_fiber', '30');
 | 
					 | 
				
			||||||
          should(data[0]).have.property('carbon_fiber', '0');
 | 
					 | 
				
			||||||
          should(data[0]).have.property('status',globals.status.new);
 | 
					          should(data[0]).have.property('status',globals.status.new);
 | 
				
			||||||
          should(_.omit(data[0].numbers[0], '_id')).be.eql({color: 'black', number: ''});
 | 
					          should(_.omit(data[0].numbers[0], '_id')).be.eql({color: 'black', number: ''});
 | 
				
			||||||
          done();
 | 
					          done();
 | 
				
			||||||
@@ -767,4 +923,92 @@ describe('/material', () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('GET /material/groups', () => {
 | 
				
			||||||
 | 
					    it('returns all groups', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/material/groups',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        const json = require('../test/db.json');
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(json.collections.material_groups.length);
 | 
				
			||||||
 | 
					        should(res.body[0]).be.eql(json.collections.material_groups[0].name);
 | 
				
			||||||
 | 
					        should(res.body).matchEach(group => {
 | 
				
			||||||
 | 
					          should(group).be.type('string');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('works with an API key', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/material/groups',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        const json = require('../test/db.json');
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(json.collections.material_groups.length);
 | 
				
			||||||
 | 
					        should(res.body[0]).be.eql(json.collections.material_groups[0].name);
 | 
				
			||||||
 | 
					        should(res.body).matchEach(group => {
 | 
				
			||||||
 | 
					          should(group).be.type('string');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects unauthorized requests', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/material/groups',
 | 
				
			||||||
 | 
					        httpStatus: 401
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('GET /material/suppliers', () => {
 | 
				
			||||||
 | 
					    it('returns all suppliers', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/material/suppliers',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        const json = require('../test/db.json');
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(json.collections.material_suppliers.length);
 | 
				
			||||||
 | 
					        should(res.body[0]).be.eql(json.collections.material_suppliers[0].name);
 | 
				
			||||||
 | 
					        should(res.body).matchEach(supplier => {
 | 
				
			||||||
 | 
					          should(supplier).be.type('string');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('works with an API key', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/material/suppliers',
 | 
				
			||||||
 | 
					        auth: {key: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        const json = require('../test/db.json');
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(json.collections.material_suppliers.length);
 | 
				
			||||||
 | 
					        should(res.body[0]).be.eql(json.collections.material_suppliers[0].name);
 | 
				
			||||||
 | 
					        should(res.body).matchEach(supplier => {
 | 
				
			||||||
 | 
					          should(supplier).be.type('string');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects unauthorized requests', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/material/suppliers',
 | 
				
			||||||
 | 
					        httpStatus: 401
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -4,10 +4,13 @@ import _ from 'lodash';
 | 
				
			|||||||
import MaterialValidate from './validate/material';
 | 
					import MaterialValidate from './validate/material';
 | 
				
			||||||
import MaterialModel from '../models/material'
 | 
					import MaterialModel from '../models/material'
 | 
				
			||||||
import SampleModel from '../models/sample';
 | 
					import SampleModel from '../models/sample';
 | 
				
			||||||
 | 
					import MaterialGroupModel from '../models/material_groups';
 | 
				
			||||||
 | 
					import MaterialSupplierModel from '../models/material_suppliers';
 | 
				
			||||||
import IdValidate from './validate/id';
 | 
					import IdValidate from './validate/id';
 | 
				
			||||||
import res400 from './validate/res400';
 | 
					import res400 from './validate/res400';
 | 
				
			||||||
import mongoose from 'mongoose';
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
import globals from '../globals';
 | 
					import globals from '../globals';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,17 +19,19 @@ const router = express.Router();
 | 
				
			|||||||
router.get('/materials', (req, res, next) => {
 | 
					router.get('/materials', (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MaterialModel.find({status:globals.status.validated}).lean().exec((err, data) => {
 | 
					  MaterialModel.find({status:globals.status.validated}).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json(_.compact(data.map(e => MaterialValidate.output(e))));  // validate all and filter null values from validation errors
 | 
					    res.json(_.compact(data.map(e => MaterialValidate.output(e))));  // validate all and filter null values from validation errors
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/materials/:group(new|deleted)', (req, res, next) => {
 | 
					router.get('/materials/:state(new|deleted)', (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MaterialModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => {
 | 
					  MaterialModel.find({status: globals.status[req.params.state]}).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json(_.compact(data.map(e => MaterialValidate.output(e))));  // validate all and filter null values from validation errors
 | 
					    res.json(_.compact(data.map(e => MaterialValidate.output(e))));  // validate all and filter null values from validation errors
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -34,12 +39,13 @@ router.get('/materials/:group(new|deleted)', (req, res, next) => {
 | 
				
			|||||||
router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
					router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MaterialModel.findById(req.params.id).lean().exec((err, data: any) => {
 | 
					  MaterialModel.findById(req.params.id).populate('group_id').populate('supplier_id').lean().exec((err, data: any) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!data) {
 | 
					    if (!data) {
 | 
				
			||||||
      return res.status(404).json({status: 'Not found'});
 | 
					      return res.status(404).json({status: 'Not found'});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (data.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return;  // deleted materials only available for maintain/admin
 | 
					    if (data.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return;  // deleted materials only available for maintain/admin
 | 
				
			||||||
    res.json(MaterialValidate.output(data));
 | 
					    res.json(MaterialValidate.output(data));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -48,7 +54,7 @@ router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
					router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const {error, value: material} = MaterialValidate.input(req.body, 'change');
 | 
					  let {error, value: material} = MaterialValidate.input(req.body, 'change');
 | 
				
			||||||
  if (error) return res400(error, res);
 | 
					  if (error) return res400(error, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => {
 | 
					  MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => {
 | 
				
			||||||
@@ -61,13 +67,21 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
    if (material.hasOwnProperty('name') && material.name !== materialData.name) {
 | 
					    if (material.hasOwnProperty('name') && material.name !== materialData.name) {
 | 
				
			||||||
      if (!await nameCheck(material, res, next)) return;
 | 
					      if (!await nameCheck(material, res, next)) return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (material.hasOwnProperty('group')) {
 | 
				
			||||||
 | 
					      material = await groupResolve(material, req, next);
 | 
				
			||||||
 | 
					      if (!material) return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (material.hasOwnProperty('supplier')) {
 | 
				
			||||||
 | 
					      material = await supplierResolve(material, req, next);
 | 
				
			||||||
 | 
					      if (!material) return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // check for changes
 | 
					    // check for changes
 | 
				
			||||||
    if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), 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}).lean().exec((err, data) => {
 | 
					    await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
      res.json(MaterialValidate.output(data));
 | 
					      res.json(MaterialValidate.output(data));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -83,7 +97,7 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
    if (data.length) {
 | 
					    if (data.length) {
 | 
				
			||||||
      return res.status(400).json({status: 'Material still in use'});
 | 
					      return res.status(400).json({status: 'Material still in use'});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    MaterialModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec((err, data) => {
 | 
					    MaterialModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).log(req).populate('group_id').populate('supplier_id').lean().exec((err, data) => {
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
      if (data) {
 | 
					      if (data) {
 | 
				
			||||||
        res.json({status: 'OK'});
 | 
					        res.json({status: 'OK'});
 | 
				
			||||||
@@ -98,31 +112,58 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
router.put('/material/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
					router.put('/material/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MaterialModel.findByIdAndUpdate(req.params.id, {status: globals.status.new}).lean().exec((err, data) => {
 | 
					  setStatus(globals.status.new, req, res, next);
 | 
				
			||||||
    if (err) return next(err);
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!data) {
 | 
					router.put('/material/validate/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
      return res.status(404).json({status: 'Not found'});
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
    }
 | 
					
 | 
				
			||||||
    res.json({status: 'OK'});
 | 
					  setStatus(globals.status.validated, req, res, next);
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.post('/material/new', async (req, res, next) => {
 | 
					router.post('/material/new', async (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const {error, value: material} = MaterialValidate.input(req.body, 'new');
 | 
					  let {error, value: material} = MaterialValidate.input(req.body, 'new');
 | 
				
			||||||
  if (error) return res400(error, res);
 | 
					  if (error) return res400(error, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!await nameCheck(material, res, next)) return;
 | 
					  if (!await nameCheck(material, res, next)) return;
 | 
				
			||||||
 | 
					  material = await groupResolve(material, req, next);
 | 
				
			||||||
 | 
					  if (!material) return;
 | 
				
			||||||
 | 
					  material = await supplierResolve(material, req, next);
 | 
				
			||||||
 | 
					  if (!material) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  material.status = globals.status.new;  // set status to new
 | 
					  material.status = globals.status.new;  // set status to new
 | 
				
			||||||
  await new MaterialModel(material).save((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());
 | 
				
			||||||
 | 
					    await data.populate('group_id').populate('supplier_id').execPopulate().catch(err => next(err));
 | 
				
			||||||
 | 
					    if (data instanceof Error) return;
 | 
				
			||||||
    res.json(MaterialValidate.output(data.toObject()));
 | 
					    res.json(MaterialValidate.output(data.toObject()));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.get('/material/groups', (req, res, next) => {
 | 
				
			||||||
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MaterialGroupModel.find().lean().exec((err, data: any) => {
 | 
				
			||||||
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name))));  // validate all and filter null values from validation errors
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.get('/material/suppliers', (req, res, next) => {
 | 
				
			||||||
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MaterialSupplierModel.find().lean().exec((err, data: any) => {
 | 
				
			||||||
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json(_.compact(data.map(e => MaterialValidate.outputSuppliers(e.name))));  // validate all and filter null values from validation errors
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = router;
 | 
					module.exports = router;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -136,3 +177,30 @@ async function nameCheck (material, res, next) {  // check if name was already t
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function groupResolve (material, req, next) {
 | 
				
			||||||
 | 
					  const groupData = await MaterialGroupModel.findOneAndUpdate({name: material.group}, {name: material.group}, {upsert: true, new: true}).log(req).lean().exec().catch(err => next(err)) as any;
 | 
				
			||||||
 | 
					  if (groupData instanceof Error) return false;
 | 
				
			||||||
 | 
					  material.group_id = groupData._id;
 | 
				
			||||||
 | 
					  delete material.group;
 | 
				
			||||||
 | 
					  return material;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function supplierResolve (material, req, next) {
 | 
				
			||||||
 | 
					  const supplierData = await MaterialSupplierModel.findOneAndUpdate({name: material.supplier}, {name: material.supplier}, {upsert: true, new: true}).log(req).lean().exec().catch(err => next(err)) as any;
 | 
				
			||||||
 | 
					  if (supplierData instanceof Error) return false;
 | 
				
			||||||
 | 
					  material.supplier_id = supplierData._id;
 | 
				
			||||||
 | 
					  delete material.supplier;
 | 
				
			||||||
 | 
					  return material;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function setStatus (status, req, res, next) {  // set measurement status
 | 
				
			||||||
 | 
					  MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
 | 
					      return res.status(404).json({status: 'Not found'});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.json({status: 'OK'});
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -138,6 +138,23 @@ describe('/measurement', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/800000000000000000000001',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {values: {dpt: [[1,2],[3,4],[5,6]]}},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'measurements',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            measurement_template: '300000000000000000000001',
 | 
				
			||||||
 | 
					            sample_id: '400000000000000000000001',
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('allows changing only one value', done => {
 | 
					    it('allows changing only one value', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -296,7 +313,7 @@ describe('/measurement', () => {
 | 
				
			|||||||
        method: 'delete',
 | 
					        method: 'delete',
 | 
				
			||||||
        url: '/measurement/800000000000000000000001',
 | 
					        url: '/measurement/800000000000000000000001',
 | 
				
			||||||
        auth: {basic: 'janedoe'},
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
        httpStatus: 200,
 | 
					        httpStatus: 200
 | 
				
			||||||
      }).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'});
 | 
				
			||||||
@@ -307,6 +324,20 @@ describe('/measurement', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'delete',
 | 
				
			||||||
 | 
					        url: '/measurement/800000000000000000000001',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'measurements',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            status: -1
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an API key', done => {
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'delete',
 | 
					        method: 'delete',
 | 
				
			||||||
@@ -383,6 +414,21 @@ describe('/measurement', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/restore/800000000000000000000004',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'measurements',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an API key', done => {
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -420,6 +466,76 @@ describe('/measurement', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('PUT /measurement/validate/{id}', () => {
 | 
				
			||||||
 | 
					    it('sets the status', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/validate/800000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done (err);
 | 
				
			||||||
 | 
					        should(res.body).be.eql({status: 'OK'});
 | 
				
			||||||
 | 
					        MeasurementModel.findById('800000000000000000000003').lean().exec((err, data: any) => {
 | 
				
			||||||
 | 
					          if (err) return done(err);
 | 
				
			||||||
 | 
					          should(data).have.property('status',globals.status.validated);
 | 
				
			||||||
 | 
					          done();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/validate/800000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'measurements',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            status: 10
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/validate/800000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {key: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 401,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects a write user', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/validate/800000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 403,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('returns 404 for an unknown sample', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/validate/000000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 404,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects unauthorized requests', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/measurement/validate/800000000000000000000003',
 | 
				
			||||||
 | 
					        httpStatus: 401,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('POST /measurement/new', () => {
 | 
					  describe('POST /measurement/new', () => {
 | 
				
			||||||
    it('returns the right measurement', done => {
 | 
					    it('returns the right measurement', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
@@ -462,6 +578,21 @@ describe('/measurement', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/measurement/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'measurements',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an invalid sample id', done => {
 | 
					    it('rejects an invalid sample id', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
@@ -531,14 +662,24 @@ describe('/measurement', () => {
 | 
				
			|||||||
        done();
 | 
					        done();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects no values', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/measurement/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {sample_id: '400000000000000000000001', values: {}, measurement_template: '300000000000000000000002'},
 | 
				
			||||||
 | 
					        res: {status: 'At least one value is required'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects a value not in the value range', done => {
 | 
					    it('rejects a value not in the value range', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
        url: '/measurement/new',
 | 
					        url: '/measurement/new',
 | 
				
			||||||
        auth: {basic: 'janedoe'},
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
        httpStatus: 400,
 | 
					        httpStatus: 400,
 | 
				
			||||||
        req: {sample_id: '400000000000000000000001', values: {val1: 4}, measurement_template: '300000000000000000000003'},
 | 
					        req: {sample_id: '400000000000000000000001', values: {val2: 5}, measurement_template: '300000000000000000000004'},
 | 
				
			||||||
        res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3, null]'}
 | 
					        res: {status: 'Invalid body format', details: '"val2" must be one of [1, 2, 3, 4, null]'}
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it('rejects a value below minimum range', done => {
 | 
					    it('rejects a value below minimum range', done => {
 | 
				
			||||||
@@ -609,6 +750,16 @@ describe('/measurement', () => {
 | 
				
			|||||||
        done();
 | 
					        done();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an old version of a measurement template', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/measurement/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {sample_id: '400000000000000000000001', values: {val1: 2}, measurement_template: '300000000000000000000003'},
 | 
				
			||||||
 | 
					        res: {status: 'Old template version not allowed'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an API key', done => {
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import IdValidate from './validate/id';
 | 
				
			|||||||
import res400 from './validate/res400';
 | 
					import res400 from './validate/res400';
 | 
				
			||||||
import ParametersValidate from './validate/parameters';
 | 
					import ParametersValidate from './validate/parameters';
 | 
				
			||||||
import globals from '../globals';
 | 
					import globals from '../globals';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = express.Router();
 | 
					const router = express.Router();
 | 
				
			||||||
@@ -56,7 +57,7 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!await templateCheck(measurement, 'change', res, next)) return;
 | 
					  if (!await templateCheck(measurement, 'change', res, next)) return;
 | 
				
			||||||
  await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}).lean().exec((err, data) => {
 | 
					  await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}).log(req).lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    res.json(MeasurementValidate.output(data));
 | 
					    res.json(MeasurementValidate.output(data));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -71,7 +72,7 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
      return res.status(404).json({status: 'Not found'});
 | 
					      return res.status(404).json({status: 'Not found'});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!await sampleIdCheck(data, req, res, next)) return;
 | 
					    if (!await sampleIdCheck(data, req, res, next)) return;
 | 
				
			||||||
    await MeasurementModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => {
 | 
					    await MeasurementModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).log(req).lean().exec(err => {
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
      return res.json({status: 'OK'});
 | 
					      return res.json({status: 'OK'});
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -81,14 +82,13 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
router.put('/measurement/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
					router.put('/measurement/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MeasurementModel.findByIdAndUpdate(req.params.id, {status: globals.status.new}).lean().exec((err, data) => {
 | 
					  setStatus(globals.status.new, req, res, next);
 | 
				
			||||||
    if (err) return next(err);
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!data) {
 | 
					router.put('/measurement/validate/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
      return res.status(404).json({status: 'Not found'});
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
    }
 | 
					
 | 
				
			||||||
    res.json({status: 'OK'});
 | 
					  setStatus(globals.status.validated, req, res, next);
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.post('/measurement/new', async (req, res, next) => {
 | 
					router.post('/measurement/new', async (req, res, next) => {
 | 
				
			||||||
@@ -104,6 +104,7 @@ router.post('/measurement/new', async (req, res, next) => {
 | 
				
			|||||||
  measurement.status = 0;
 | 
					  measurement.status = 0;
 | 
				
			||||||
  await new MeasurementModel(measurement).save((err, data) => {
 | 
					  await new MeasurementModel(measurement).save((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					    db.log(req, 'measurements', {_id: data._id}, data.toObject());
 | 
				
			||||||
    res.json(MeasurementValidate.output(data.toObject()));
 | 
					    res.json(MeasurementValidate.output(data.toObject()));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -131,6 +132,14 @@ async function templateCheck (measurement, param, res, next) {  // validate meas
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // 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
 | 
				
			||||||
 | 
					    const templateVersions = await MeasurementTemplateModel.find({first_id: templateData.first_id}).sort({version: -1}).lean().exec().catch(err => next(err)) as any;
 | 
				
			||||||
 | 
					    if (templateVersions instanceof Error) return false;
 | 
				
			||||||
 | 
					    if (measurement.measurement_template !== templateVersions[0]._id.toString()) {  // template not latest
 | 
				
			||||||
 | 
					      res.status(400).json({status: 'Old template version not allowed'});
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (Object.keys(measurement.values).length === 0) {
 | 
					    if (Object.keys(measurement.values).length === 0) {
 | 
				
			||||||
      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
 | 
				
			||||||
@@ -147,3 +156,14 @@ async function templateCheck (measurement, param, res, next) {  // validate meas
 | 
				
			|||||||
  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
 | 
				
			||||||
 | 
					  MeasurementModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
 | 
					      return res.status(404).json({status: 'Not found'});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.json({status: 'OK'});
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import TestHelper from "../test/helper";
 | 
					import TestHelper from "../test/helper";
 | 
				
			||||||
 | 
					import should from 'should/as-function';
 | 
				
			||||||
import db from '../db';
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,6 +21,121 @@ describe('/', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('GET /changelog/{timestamp}/{page}/{pagesize}', () => {
 | 
				
			||||||
 | 
					    it('returns the first page', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/0/2',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(2);
 | 
				
			||||||
 | 
					        should(res.body[0].date).be.eql('1979-07-28T06:04:51.000Z');
 | 
				
			||||||
 | 
					        should(res.body[1].date).be.eql('1979-07-28T06:04:50.000Z');
 | 
				
			||||||
 | 
					        should(res.body).matchEach(log => {
 | 
				
			||||||
 | 
					          should(log).have.only.keys('date', 'action', 'collection', 'conditions', 'data');
 | 
				
			||||||
 | 
					          should(log).have.property('action', 'PUT /sample/400000000000000000000001');
 | 
				
			||||||
 | 
					          should(log).have.property('collection', 'samples');
 | 
				
			||||||
 | 
					          should(log).have.property('conditions', {_id: '400000000000000000000001'});
 | 
				
			||||||
 | 
					          should(log).have.property('data', {type: 'part', status: 0});
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('returns another page', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/1/2',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(1);
 | 
				
			||||||
 | 
					        should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z');
 | 
				
			||||||
 | 
					        should(res.body).matchEach(log => {
 | 
				
			||||||
 | 
					          should(log).have.only.keys('date', 'action', 'collection', 'conditions', 'data');
 | 
				
			||||||
 | 
					          should(log).have.property('action', 'PUT /sample/400000000000000000000001');
 | 
				
			||||||
 | 
					          should(log).have.property('collection', 'samples');
 | 
				
			||||||
 | 
					          should(log).have.property('conditions', {_id: '400000000000000000000001'});
 | 
				
			||||||
 | 
					          should(log).have.property('data', {type: 'part', status: 0});
 | 
				
			||||||
 | 
					          done();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('returns an empty array for a page with no results', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(0);
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects timestamps pre unix epoch', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1879-07-28T06:04:51.000Z/10/2',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        res: {status: 'Invalid body format', details: '"timestamp" must be larger than or equal to "1970-01-01T00:00:00.000Z"'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects invalid timestamps', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-14-28T06:04:51.000Z/10/2',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        res: {status: 'Invalid body format', details: '"timestamp" must be in ISO 8601 date format'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects negative page numbers', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/-10/2',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        res: {status: 'Invalid body format', details: '"page" must be larger than or equal to 0'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects negative pagesizes', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/10/-2',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        res: {status: 'Invalid body format', details: '"pagesize" must be larger than or equal to 0'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects request from a write user', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 403
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects requests from an API key', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
 | 
				
			||||||
 | 
					        auth: {key: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 401
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects unauthorized requests', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
 | 
				
			||||||
 | 
					        httpStatus: 401
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('Unknown routes', () => {
 | 
					  describe('Unknown routes', () => {
 | 
				
			||||||
    it('return a 404 message', done => {
 | 
					    it('return a 404 message', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,10 @@
 | 
				
			|||||||
import express from 'express';
 | 
					import express from 'express';
 | 
				
			||||||
import globals from '../globals';
 | 
					import globals from '../globals';
 | 
				
			||||||
 | 
					import RootValidate from './validate/root';
 | 
				
			||||||
 | 
					import res400 from './validate/res400';
 | 
				
			||||||
 | 
					import ChangelogModel from '../models/changelog';
 | 
				
			||||||
 | 
					import mongoose from 'mongoose';
 | 
				
			||||||
 | 
					import _ from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = express.Router();
 | 
					const router = express.Router();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,4 +17,18 @@ router.get('/authorized', (req, res) => {
 | 
				
			|||||||
  res.json({status: 'Authorization successful', method: req.authDetails.method});
 | 
					  res.json({status: 'Authorization successful', method: req.authDetails.method});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.get('/changelog/:timestamp/:page?/:pagesize?', (req, res, next) => {
 | 
				
			||||||
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {error, value: options} = RootValidate.changelogParams({timestamp: req.params.timestamp, page: req.params.page, pagesize: req.params.pagesize});
 | 
				
			||||||
 | 
					  if (error) return res400(error, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const id = new mongoose.Types.ObjectId(Math.floor(new Date(options.timestamp).getTime() / 1000).toString(16) + '0000000000000000');
 | 
				
			||||||
 | 
					  ChangelogModel.find({_id: {$lte: id}}).sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json(_.compact(data.map(e => RootValidate.changelogOutput(e))));  // validate all and filter null values from validation errors
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = router;
 | 
					module.exports = router;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,11 +9,7 @@ import mongoose from 'mongoose';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// TODO: generate output for ML in format DPT -> data, implement filtering, field selection
 | 
					// TODO: generate output for ML in format DPT -> data, implement filtering, field selection
 | 
				
			||||||
// TODO: generate csv
 | 
					// TODO: generate csv
 | 
				
			||||||
// TODO: filter by not completely filled/no measurements
 | 
					 | 
				
			||||||
// TODO: write script for data import
 | 
					// TODO: write script for data import
 | 
				
			||||||
// TODO: allow adding sample numbers for existing samples
 | 
					 | 
				
			||||||
// TODO: Do not allow validation or measurement entry without condition
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('/sample', () => {
 | 
					describe('/sample', () => {
 | 
				
			||||||
  let server;
 | 
					  let server;
 | 
				
			||||||
@@ -84,7 +80,7 @@ describe('/sample', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('GET /samples/{group}', () => {
 | 
					  describe('GET /samples/{state}', () => {
 | 
				
			||||||
    it('returns all new samples', done => {
 | 
					    it('returns all new samples', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'get',
 | 
					        method: 'get',
 | 
				
			||||||
@@ -375,6 +371,22 @@ describe('/sample', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/400000000000000000000001',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {type: 'part', color: 'signalviolet', batch: '114531', condition: {condition_template: '200000000000000000000003'}, material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'samples',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          dataIgn: ['notes', 'note_id']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('adjusts the note_fields correctly', done => {
 | 
					    it('adjusts the note_fields correctly', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -455,6 +467,16 @@ describe('/sample', () => {
 | 
				
			|||||||
        res: {status: 'Color not available for material'}
 | 
					        res: {status: 'Color not available for material'}
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an undefined color for the same material', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/400000000000000000000001',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {type: 'part', color: 'signalviolet', batch: '114531', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
				
			||||||
 | 
					        res: {status: 'Color not available for material'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an unknown material id', done => {
 | 
					    it('rejects an unknown material id', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -574,6 +596,26 @@ describe('/sample', () => {
 | 
				
			|||||||
        res: {_id: '400000000000000000000006', number: 'Rng36', type: 'granulate', color: 'black', batch: '', condition: {}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
 | 
					        res: {_id: '400000000000000000000006', number: 'Rng36', type: 'granulate', color: 'black', batch: '', condition: {}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an old version of a condition template', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/400000000000000000000001',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {condition: {p1: 36, condition_template: '200000000000000000000004'}},
 | 
				
			||||||
 | 
					        res: {status: 'Old template version not allowed'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('allows keeping an old version of a condition template', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/400000000000000000000004',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {condition: {p1: 36, condition_template: '200000000000000000000004'}},
 | 
				
			||||||
 | 
					        res: {_id: '400000000000000000000004', number: '32', type: 'granulate', color: 'black', batch: '1653000308', condition: {p1: 36, condition_template: '200000000000000000000004'}, material_id: '100000000000000000000005', note_id: '500000000000000000000003', user_id: '000000000000000000000003'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an changing back to an empty condition', done => {
 | 
					    it('rejects an changing back to an empty condition', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -678,6 +720,19 @@ describe('/sample', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'delete',
 | 
				
			||||||
 | 
					        url: '/sample/400000000000000000000001',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'samples',
 | 
				
			||||||
 | 
					          skip: 1,
 | 
				
			||||||
 | 
					          dataAdd: {status: -1}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('keeps the notes of the sample', done => {
 | 
					    it('keeps the notes of the sample', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'delete',
 | 
					        method: 'delete',
 | 
				
			||||||
@@ -838,6 +893,24 @@ describe('/sample', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/restore/400000000000000000000005',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'samples',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            group_id: '900000000000000000000002',
 | 
				
			||||||
 | 
					            supplier_id: '110000000000000000000002',
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          dataIgn: ['group_id', 'supplier_id']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an API key', done => {
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -875,6 +948,99 @@ describe('/sample', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('PUT /sample/validate/{id}', () => {
 | 
				
			||||||
 | 
					    it('sets the status', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/400000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done (err);
 | 
				
			||||||
 | 
					        should(res.body).be.eql({status: 'OK'});
 | 
				
			||||||
 | 
					        SampleModel.findById('400000000000000000000003').lean().exec((err, data: any) => {
 | 
				
			||||||
 | 
					          if (err) return done(err);
 | 
				
			||||||
 | 
					          should(data).have.property('status',globals.status.validated);
 | 
				
			||||||
 | 
					          done();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/400000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'samples',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            group_id: '900000000000000000000002',
 | 
				
			||||||
 | 
					            supplier_id: '110000000000000000000002',
 | 
				
			||||||
 | 
					            status: 10
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          dataIgn: ['group_id', 'supplier_id']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects validating a sample without condition', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/400000000000000000000006',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        res: {status: 'Sample without condition cannot be valid'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects validating a sample without measurements', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/400000000000000000000004',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {},
 | 
				
			||||||
 | 
					        res: {status: 'Sample without measurements cannot be valid'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/400000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {key: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 401,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects a write user', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/400000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 403,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('returns 404 for an unknown sample', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/000000000000000000000003',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 404,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects unauthorized requests', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/sample/validate/400000000000000000000003',
 | 
				
			||||||
 | 
					        httpStatus: 401,
 | 
				
			||||||
 | 
					        req: {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('POST /sample/new', () => {
 | 
					  describe('POST /sample/new', () => {
 | 
				
			||||||
    it('returns the right sample', done => {
 | 
					    it('returns the right sample', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
@@ -934,6 +1100,24 @@ describe('/sample', () => {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/sample/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'samples',
 | 
				
			||||||
 | 
					          dataAdd: {
 | 
				
			||||||
 | 
					            number: 'Rng37',
 | 
				
			||||||
 | 
					            user_id: '000000000000000000000002',
 | 
				
			||||||
 | 
					            status: 0
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          dataIgn: ['notes', 'note_id']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('stores the custom fields', done => {
 | 
					    it('stores the custom fields', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
@@ -1034,7 +1218,7 @@ describe('/sample', () => {
 | 
				
			|||||||
        res: {status: 'Material not available'}
 | 
					        res: {status: 'Material not available'}
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it('rejects a sample number', done => {
 | 
					    it('rejects a sample number for a write user', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
        url: '/sample/new',
 | 
					        url: '/sample/new',
 | 
				
			||||||
@@ -1044,6 +1228,38 @@ describe('/sample', () => {
 | 
				
			|||||||
        res: {status: 'Invalid body format', details: '"number" is not allowed'}
 | 
					        res: {status: 'Invalid body format', details: '"number" is not allowed'}
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('allows a sample number for an admin user', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/sample/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {number: 'Rng34', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done (err);
 | 
				
			||||||
 | 
					        should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
 | 
				
			||||||
 | 
					        should(res.body).have.property('_id').be.type('string');
 | 
				
			||||||
 | 
					        should(res.body).have.property('number', 'Rng34');
 | 
				
			||||||
 | 
					        should(res.body).have.property('color', 'black');
 | 
				
			||||||
 | 
					        should(res.body).have.property('type', 'granulate');
 | 
				
			||||||
 | 
					        should(res.body).have.property('batch', '1560237365');
 | 
				
			||||||
 | 
					        should(res.body).have.property('condition', {});
 | 
				
			||||||
 | 
					        should(res.body).have.property('material_id', '100000000000000000000001');
 | 
				
			||||||
 | 
					        should(res.body).have.property('note_id').be.type('string');
 | 
				
			||||||
 | 
					        should(res.body).have.property('user_id', '000000000000000000000003');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an existing sample number for an admin user', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/sample/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {number: 'Rng33', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
				
			||||||
 | 
					        res: {status: 'Sample number already taken'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects an invalid sample reference', done => {
 | 
					    it('rejects an invalid sample reference', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
@@ -1134,6 +1350,16 @@ describe('/sample', () => {
 | 
				
			|||||||
        res: {status: 'Condition template not available'}
 | 
					        res: {status: 'Condition template not available'}
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an old version of a condition template', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/sample/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 400,
 | 
				
			||||||
 | 
					        req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {p1: 36, condition_template: '200000000000000000000004'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
				
			||||||
 | 
					        res: {status: 'Old template version not allowed'}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects a missing color', done => {
 | 
					    it('rejects a missing color', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import mongoose from 'mongoose';
 | 
				
			|||||||
import ConditionTemplateModel from '../models/condition_template';
 | 
					import ConditionTemplateModel from '../models/condition_template';
 | 
				
			||||||
import ParametersValidate from './validate/parameters';
 | 
					import ParametersValidate from './validate/parameters';
 | 
				
			||||||
import globals from '../globals';
 | 
					import globals from '../globals';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = express.Router();
 | 
					const router = express.Router();
 | 
				
			||||||
@@ -27,10 +28,10 @@ router.get('/samples', (req, res, next) => {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/samples/:group(new|deleted)', (req, res, next) => {
 | 
					router.get('/samples/:state(new|deleted)', (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SampleModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => {
 | 
					  SampleModel.find({status: globals.status[req.params.state]}).lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    res.json(_.compact(data.map(e => SampleValidate.output(e))));  // validate all and filter null values from validation errors
 | 
					    res.json(_.compact(data.map(e => SampleValidate.output(e))));  // validate all and filter null values from validation errors
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -39,12 +40,18 @@ router.get('/samples/:group(new|deleted)', (req, res, next) => {
 | 
				
			|||||||
router.get('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
					router.get('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SampleModel.findById(req.params.id).populate('material_id').populate('user_id', 'name').populate('note_id').lean().exec((err, sampleData: any) => {
 | 
					  SampleModel.findById(req.params.id).populate('material_id').populate('user_id', 'name').populate('note_id').exec(async (err, sampleData: any) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (sampleData) {
 | 
					    if (sampleData) {
 | 
				
			||||||
 | 
					      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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (sampleData.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return;  // deleted samples only available for maintain/admin
 | 
					      if (sampleData.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return;  // deleted samples only available for maintain/admin
 | 
				
			||||||
      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.supplier = sampleData.material.supplier_id.name;
 | 
				
			||||||
      sampleData.user = sampleData.user_id.name;
 | 
					      sampleData.user = sampleData.user_id.name;
 | 
				
			||||||
      sampleData.notes = sampleData.note_id ? sampleData.note_id : {};
 | 
					      sampleData.notes = sampleData.note_id ? sampleData.note_id : {};
 | 
				
			||||||
      MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data) => {
 | 
					      MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data) => {
 | 
				
			||||||
@@ -84,7 +91,7 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (sample.hasOwnProperty('condition') && !(_.isEmpty(sample.condition) && _.isEmpty(sampleData.condition))) {  // do not execute check if condition is and was empty
 | 
					    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;
 | 
					      if (!await conditionCheck(sample.condition, 'change', res, next, sampleData.condition.condition_template.toString() !== sample.condition.condition_template)) return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (sample.hasOwnProperty('notes')) {
 | 
					    if (sample.hasOwnProperty('notes')) {
 | 
				
			||||||
@@ -95,9 +102,9 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
        newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes);  // check if notes were changed
 | 
					        newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes);  // check if notes were changed
 | 
				
			||||||
        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);
 | 
					            customFieldsChange(Object.keys(data.custom_fields), -1, req);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          await NoteModel.findByIdAndDelete(sampleData.note_id).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);
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -106,9 +113,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
      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;
 | 
				
			||||||
        if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {  // new custom_fields
 | 
					        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);
 | 
					          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());
 | 
				
			||||||
        delete sample.notes;
 | 
					        delete sample.notes;
 | 
				
			||||||
        sample.note_id = data._id;
 | 
					        sample.note_id = data._id;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -119,7 +127,7 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
      sample.status = globals.status.new;
 | 
					      sample.status = globals.status.new;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data: any) => {
 | 
					    await SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).log(req).lean().exec((err, data: any) => {
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
      res.json(SampleValidate.output(data));
 | 
					      res.json(SampleValidate.output(data));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -139,18 +147,18 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
    // only maintain and admin are allowed to edit other user's data
 | 
					    // 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 (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await SampleModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => {  // set sample status
 | 
					    await SampleModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).log(req).lean().exec(err => {  // set sample status
 | 
				
			||||||
      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.update({sample_id: mongoose.Types.ObjectId(req.params.id)}, {status: -1}).lean().exec(err => {
 | 
					      MeasurementModel.updateMany({sample_id: mongoose.Types.ObjectId(req.params.id)}, {status: -1}).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);
 | 
					              customFieldsChange(Object.keys(data.custom_fields), -1, req);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            res.json({status: 'OK'});
 | 
					            res.json({status: 'OK'});
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
@@ -166,7 +174,7 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
router.put('/sample/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
					router.put('/sample/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SampleModel.findByIdAndUpdate(req.params.id, {status: globals.status.new}).lean().exec((err, data) => {
 | 
					  SampleModel.findByIdAndUpdate(req.params.id, {status: globals.status.new}).log(req).lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!data) {
 | 
					    if (!data) {
 | 
				
			||||||
@@ -176,6 +184,34 @@ router.put('/sample/restore/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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}).log(req).lean().exec(err => {
 | 
				
			||||||
 | 
					        if (err) return next(err);
 | 
				
			||||||
 | 
					        res.json({status: 'OK'});
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.post('/sample/new', async (req, res, next) => {
 | 
					router.post('/sample/new', async (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -183,14 +219,14 @@ router.post('/sample/new', async (req, res, next) => {
 | 
				
			|||||||
    req.body.condition = {};
 | 
					    req.body.condition = {};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const {error, value: sample} = SampleValidate.input(req.body, 'new');
 | 
					  const {error, value: sample} = SampleValidate.input(req.body, 'new' + (req.authDetails.level === 'admin' ? '-admin' : ''));
 | 
				
			||||||
  if (error) return res400(error, res);
 | 
					  if (error) return res400(error, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {  // new custom_fields
 | 
					  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);
 | 
					    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
 | 
				
			||||||
@@ -198,17 +234,24 @@ router.post('/sample/new', async (req, res, next) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sample.status = globals.status.new;  // set status to new
 | 
					  sample.status = globals.status.new;  // set status to new
 | 
				
			||||||
  sample.number = await numberGenerate(sample, req, res, next);
 | 
					  if (sample.hasOwnProperty('number')) {
 | 
				
			||||||
 | 
					    if (!await numberCheck(sample, res, next)) return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  else {
 | 
				
			||||||
 | 
					    sample.number = await numberGenerate(sample, 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());
 | 
				
			||||||
    delete sample.notes;
 | 
					    delete sample.notes;
 | 
				
			||||||
    sample.note_id = data._id;
 | 
					    sample.note_id = data._id;
 | 
				
			||||||
    sample.user_id = req.authDetails.id;
 | 
					    sample.user_id = req.authDetails.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new SampleModel(sample).save((err, data) => {
 | 
					    new SampleModel(sample).save((err, data) => {
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
 | 
					      db.log(req, 'samples', {_id: data._id}, data.toObject());
 | 
				
			||||||
      res.json(SampleValidate.output(data.toObject()));
 | 
					      res.json(SampleValidate.output(data.toObject()));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -238,6 +281,15 @@ async function numberGenerate (sample, req, res, next) {  // generate number in
 | 
				
			|||||||
  return req.authDetails.location + (sampleData ? Number(sampleData.number.replace(/[^0-9]+/g, '')) + 1 : 1);
 | 
					  return req.authDetails.location + (sampleData ? Number(sampleData.number.replace(/[^0-9]+/g, '')) + 1 : 1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function numberCheck(sample, res, next) {
 | 
				
			||||||
 | 
					  const sampleData = await SampleModel.findOne({number: sample.number}).lean().exec().catch(err => {next(err); return false;});
 | 
				
			||||||
 | 
					  if (sampleData) {  // found entry with sample number
 | 
				
			||||||
 | 
					    res.status(400).json({status: 'Sample number already taken'});
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function materialCheck (sample, res, next, id = sample.material_id) {  // validate material_id and color, returns false if invalid
 | 
					async function materialCheck (sample, res, next, id = sample.material_id) {  // validate material_id and color, returns false if invalid
 | 
				
			||||||
  const materialData = await MaterialModel.findById(id).lean().exec().catch(err => next(err)) as any;
 | 
					  const materialData = await MaterialModel.findById(id).lean().exec().catch(err => next(err)) as any;
 | 
				
			||||||
  if (materialData instanceof Error) return false;
 | 
					  if (materialData instanceof Error) return false;
 | 
				
			||||||
@@ -252,7 +304,7 @@ async function materialCheck (sample, res, next, id = sample.material_id) {  //
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function conditionCheck (condition, param, res, next) {  // validate treatment template, returns false if invalid, otherwise template data
 | 
					async function conditionCheck (condition, param, res, next, checkVersion = true) {  // validate treatment template, returns false if invalid, otherwise template data
 | 
				
			||||||
  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;
 | 
				
			||||||
@@ -264,6 +316,16 @@ async function conditionCheck (condition, param, res, next) {  // validate treat
 | 
				
			|||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (checkVersion) {
 | 
				
			||||||
 | 
					    // get all template versions and check if given is latest
 | 
				
			||||||
 | 
					    const conditionVersions = await ConditionTemplateModel.find({first_id: conditionData.first_id}).sort({version: -1}).lean().exec().catch(err => next(err)) as any;
 | 
				
			||||||
 | 
					    if (conditionVersions instanceof Error) return false;
 | 
				
			||||||
 | 
					    if (condition.condition_template !== conditionVersions[0]._id.toString()) {  // template not latest
 | 
				
			||||||
 | 
					      res.status(400).json({status: 'Old template version not allowed'});
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // validate parameters
 | 
					  // validate parameters
 | 
				
			||||||
  const {error, value: ignore} = ParametersValidate.input(_.omit(condition, 'condition_template'), conditionData.parameters, param);
 | 
					  const {error, value: ignore} = ParametersValidate.input(_.omit(condition, 'condition_template'), conditionData.parameters, param);
 | 
				
			||||||
  if (error) {res400(error, res); return false;}
 | 
					  if (error) {res400(error, res); return false;}
 | 
				
			||||||
@@ -272,7 +334,7 @@ async function conditionCheck (condition, param, res, next) {  // validate treat
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
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 => {
 | 
				
			||||||
    if (sample.notes.sample_references.length > 0) {  // there are sample_references
 | 
					    if (sample.notes.hasOwnProperty('sample_references') && sample.notes.sample_references.length > 0) {  // there are sample_references
 | 
				
			||||||
      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 => {
 | 
				
			||||||
@@ -295,17 +357,18 @@ function sampleRefCheck (sample, res, next) {  // validate sample_references, re
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function customFieldsChange (fields, amount) {  // 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}}, {new: true}).lean().exec((err, data: any) => {  // check if field exists
 | 
					    NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}} as any, {new: true}).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 => {
 | 
					        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());
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      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}).lean().exec(err => {
 | 
					        NoteFieldModel.findOneAndDelete({name: field}).log(req).lean().exec(err => {
 | 
				
			||||||
          if (err) return console.error(err);
 | 
					          if (err) return console.error(err);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,9 +4,6 @@ import TemplateConditionModel from '../models/condition_template';
 | 
				
			|||||||
import TemplateMeasurementModel from '../models/measurement_template';
 | 
					import TemplateMeasurementModel from '../models/measurement_template';
 | 
				
			||||||
import TestHelper from "../test/helper";
 | 
					import TestHelper from "../test/helper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: do not allow usage of old templates for new samples
 | 
					 | 
				
			||||||
// TODO: remove number_prefix
 | 
					 | 
				
			||||||
// TODO: template parameters are not allowed to be condition_template
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('/template', () => {
 | 
					describe('/template', () => {
 | 
				
			||||||
  let server;
 | 
					  let server;
 | 
				
			||||||
@@ -135,7 +132,8 @@ describe('/template', () => {
 | 
				
			|||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
					            TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
				
			||||||
              if (err) return done(err);
 | 
					              if (err) return done(err);
 | 
				
			||||||
              should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
 | 
					              should(data).have.only.keys('_id', 'first_id', 'name', 'version', 'parameters', '__v');
 | 
				
			||||||
 | 
					              should(data.first_id.toString()).be.eql('200000000000000000000001');
 | 
				
			||||||
              should(data).have.property('name', 'heat aging');
 | 
					              should(data).have.property('name', 'heat aging');
 | 
				
			||||||
              should(data).have.property('version', 2);
 | 
					              should(data).have.property('version', 2);
 | 
				
			||||||
              should(data).have.property('parameters').have.lengthOf(1);
 | 
					              should(data).have.property('parameters').have.lengthOf(1);
 | 
				
			||||||
@@ -146,6 +144,22 @@ describe('/template', () => {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					      it('creates a changelog', done => {
 | 
				
			||||||
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					          method: 'put',
 | 
				
			||||||
 | 
					          url: '/template/condition/200000000000000000000001',
 | 
				
			||||||
 | 
					          auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					          httpStatus: 200,
 | 
				
			||||||
 | 
					          req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]},
 | 
				
			||||||
 | 
					          log: {
 | 
				
			||||||
 | 
					            collection: 'condition_templates',
 | 
				
			||||||
 | 
					            dataAdd: {
 | 
				
			||||||
 | 
					              first_id: '200000000000000000000001',
 | 
				
			||||||
 | 
					              version: 2
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      it('allows changing only one property', done => {
 | 
					      it('allows changing only one property', done => {
 | 
				
			||||||
        TestHelper.request(server, done, {
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
          method: 'put',
 | 
					          method: 'put',
 | 
				
			||||||
@@ -157,7 +171,8 @@ describe('/template', () => {
 | 
				
			|||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
					          TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
				
			||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
 | 
					            should(data).have.only.keys('_id', 'first_id', 'name', 'version', 'parameters', '__v');
 | 
				
			||||||
 | 
					            should(data.first_id.toString()).be.eql('200000000000000000000001');
 | 
				
			||||||
            should(data).have.property('name', 'heat aging');
 | 
					            should(data).have.property('name', 'heat aging');
 | 
				
			||||||
            should(data).have.property('version', 2);
 | 
					            should(data).have.property('version', 2);
 | 
				
			||||||
            should(data).have.property('parameters').have.lengthOf(2);
 | 
					            should(data).have.property('parameters').have.lengthOf(2);
 | 
				
			||||||
@@ -219,6 +234,16 @@ describe('/template', () => {
 | 
				
			|||||||
          done();
 | 
					          done();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					      it('rejects `condition_template` as parameter name', done => {
 | 
				
			||||||
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					          method: 'put',
 | 
				
			||||||
 | 
					          url: '/template/condition/200000000000000000000001',
 | 
				
			||||||
 | 
					          auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					          httpStatus: 400,
 | 
				
			||||||
 | 
					          req: {parameters: [{name: 'condition_template', range: {}}]},
 | 
				
			||||||
 | 
					          res: {status: 'Invalid body format', details: '"parameters[0].name" contains an invalid value'}
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      it('rejects not specified parameters', done => {
 | 
					      it('rejects not specified parameters', done => {
 | 
				
			||||||
        TestHelper.request(server, done, {
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
          method: 'put',
 | 
					          method: 'put',
 | 
				
			||||||
@@ -228,7 +253,7 @@ describe('/template', () => {
 | 
				
			|||||||
          req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]},
 | 
					          req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]},
 | 
				
			||||||
          res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'}
 | 
					          res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'}
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      })
 | 
					      });
 | 
				
			||||||
      it('rejects an invalid id', done => {
 | 
					      it('rejects an invalid id', done => {
 | 
				
			||||||
        TestHelper.request(server, done, {
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
          method: 'put',
 | 
					          method: 'put',
 | 
				
			||||||
@@ -307,7 +332,8 @@ describe('/template', () => {
 | 
				
			|||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
					          TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
				
			||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
 | 
					            should(data).have.only.keys('_id', 'first_id', 'name', 'version', 'parameters', '__v');
 | 
				
			||||||
 | 
					            should(data.first_id.toString()).be.eql(data._id.toString());
 | 
				
			||||||
            should(data).have.property('name', 'heat aging');
 | 
					            should(data).have.property('name', 'heat aging');
 | 
				
			||||||
            should(data).have.property('version', 1);
 | 
					            should(data).have.property('version', 1);
 | 
				
			||||||
            should(data).have.property('parameters').have.lengthOf(1);
 | 
					            should(data).have.property('parameters').have.lengthOf(1);
 | 
				
			||||||
@@ -318,6 +344,20 @@ describe('/template', () => {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					      it('creates a changelog', done => {
 | 
				
			||||||
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					          method: 'post',
 | 
				
			||||||
 | 
					          url: '/template/condition/new',
 | 
				
			||||||
 | 
					          auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					          httpStatus: 200,
 | 
				
			||||||
 | 
					          req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]},
 | 
				
			||||||
 | 
					          log: {
 | 
				
			||||||
 | 
					            collection: 'condition_templates',
 | 
				
			||||||
 | 
					            dataAdd: {version: 1},
 | 
				
			||||||
 | 
					            dataIgn: ['first_id']
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      it('rejects a missing name', done => {
 | 
					      it('rejects a missing name', done => {
 | 
				
			||||||
        TestHelper.request(server, done, {
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
          method: 'post',
 | 
					          method: 'post',
 | 
				
			||||||
@@ -328,6 +368,16 @@ describe('/template', () => {
 | 
				
			|||||||
          res: {status: 'Invalid body format', details: '"name" is required'}
 | 
					          res: {status: 'Invalid body format', details: '"name" is required'}
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					      it('rejects `condition_template` as parameter name', done => {
 | 
				
			||||||
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					          method: 'post',
 | 
				
			||||||
 | 
					          url: '/template/condition/new',
 | 
				
			||||||
 | 
					          auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					          httpStatus: 400,
 | 
				
			||||||
 | 
					          req: {name: 'heat aging', parameters: [{name: 'condition_template', range: {min: 1}}]},
 | 
				
			||||||
 | 
					          res: {status: 'Invalid body format', details: '"parameters[0].name" contains an invalid value'}
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      it('rejects a number prefix', done => {
 | 
					      it('rejects a number prefix', done => {
 | 
				
			||||||
        TestHelper.request(server, done, {
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
          method: 'post',
 | 
					          method: 'post',
 | 
				
			||||||
@@ -532,13 +582,14 @@ describe('/template', () => {
 | 
				
			|||||||
          url: '/template/measurement/300000000000000000000001',
 | 
					          url: '/template/measurement/300000000000000000000001',
 | 
				
			||||||
          auth: {basic: 'admin'},
 | 
					          auth: {basic: 'admin'},
 | 
				
			||||||
          httpStatus: 200,
 | 
					          httpStatus: 200,
 | 
				
			||||||
          req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]},
 | 
					          req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}
 | 
				
			||||||
        }).end((err, res) => {
 | 
					        }).end((err, res) => {
 | 
				
			||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          should(_.omit(res.body, '_id')).be.eql({name: 'IR spectrum', version: 2, parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]});
 | 
					          should(_.omit(res.body, '_id')).be.eql({name: 'IR spectrum', version: 2, parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]});
 | 
				
			||||||
          TemplateMeasurementModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
					          TemplateMeasurementModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
				
			||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
 | 
					            should(data).have.only.keys('_id', 'first_id', 'name', 'version', 'parameters', '__v');
 | 
				
			||||||
 | 
					            should(data.first_id.toString()).be.eql('300000000000000000000001');
 | 
				
			||||||
            should(data).have.property('name', 'IR spectrum');
 | 
					            should(data).have.property('name', 'IR spectrum');
 | 
				
			||||||
            should(data).have.property('version', 2);
 | 
					            should(data).have.property('version', 2);
 | 
				
			||||||
            should(data).have.property('parameters').have.lengthOf(1);
 | 
					            should(data).have.property('parameters').have.lengthOf(1);
 | 
				
			||||||
@@ -550,6 +601,22 @@ describe('/template', () => {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					      it('creates a changelog', done => {
 | 
				
			||||||
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					          method: 'put',
 | 
				
			||||||
 | 
					          url: '/template/measurement/300000000000000000000001',
 | 
				
			||||||
 | 
					          auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					          httpStatus: 200,
 | 
				
			||||||
 | 
					          req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]},
 | 
				
			||||||
 | 
					          log: {
 | 
				
			||||||
 | 
					            collection: 'measurement_templates',
 | 
				
			||||||
 | 
					            dataAdd: {
 | 
				
			||||||
 | 
					              first_id: '300000000000000000000001',
 | 
				
			||||||
 | 
					              version: 2
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      it('allows changing only one property', done => {
 | 
					      it('allows changing only one property', done => {
 | 
				
			||||||
        TestHelper.request(server, done, {
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
          method: 'put',
 | 
					          method: 'put',
 | 
				
			||||||
@@ -562,7 +629,8 @@ describe('/template', () => {
 | 
				
			|||||||
          should(_.omit(res.body, '_id')).be.eql({name: 'IR spectrum', version: 2, parameters: [{name: 'dpt', range: {type: 'array'}}]});
 | 
					          should(_.omit(res.body, '_id')).be.eql({name: 'IR spectrum', version: 2, parameters: [{name: 'dpt', range: {type: 'array'}}]});
 | 
				
			||||||
          TemplateMeasurementModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
					          TemplateMeasurementModel.findById(res.body._id).lean().exec((err, data:any) => {
 | 
				
			||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
 | 
					            should(data).have.only.keys('_id', 'first_id', 'name', 'version', 'parameters', '__v');
 | 
				
			||||||
 | 
					            should(data.first_id.toString()).be.eql('300000000000000000000001');
 | 
				
			||||||
            should(data).have.property('name', 'IR spectrum');
 | 
					            should(data).have.property('name', 'IR spectrum');
 | 
				
			||||||
            should(data).have.property('version', 2);
 | 
					            should(data).have.property('version', 2);
 | 
				
			||||||
            should(data).have.property('parameters').have.lengthOf(1);
 | 
					            should(data).have.property('parameters').have.lengthOf(1);
 | 
				
			||||||
@@ -621,7 +689,7 @@ describe('/template', () => {
 | 
				
			|||||||
          req: {parameters: [{name: 'weight %', range: {}}]}
 | 
					          req: {parameters: [{name: 'weight %', range: {}}]}
 | 
				
			||||||
        }).end((err, res) => {
 | 
					        }).end((err, res) => {
 | 
				
			||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          should(_.omit(res.body, '_id')).be.eql({name: 'kf', version: 3, parameters: [{name: 'weight %', range: {}}]});
 | 
					          should(_.omit(res.body, '_id')).be.eql({name: 'kf', version: 2, parameters: [{name: 'weight %', range: {}}]});
 | 
				
			||||||
          done();
 | 
					          done();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -713,7 +781,8 @@ describe('/template', () => {
 | 
				
			|||||||
          TemplateMeasurementModel.find({name: 'vz'}).lean().exec((err, data:any) => {
 | 
					          TemplateMeasurementModel.find({name: 'vz'}).lean().exec((err, data:any) => {
 | 
				
			||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            should(data).have.lengthOf(1);
 | 
					            should(data).have.lengthOf(1);
 | 
				
			||||||
            should(data[0]).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
 | 
					            should(data[0]).have.only.keys('_id', 'first_id', 'name', 'version', 'parameters', '__v');
 | 
				
			||||||
 | 
					            should(data[0].first_id.toString()).be.eql(data[0]._id.toString());
 | 
				
			||||||
            should(data[0]).have.property('name', 'vz');
 | 
					            should(data[0]).have.property('name', 'vz');
 | 
				
			||||||
            should(data[0]).have.property('version', 1);
 | 
					            should(data[0]).have.property('version', 1);
 | 
				
			||||||
            should(data[0]).have.property('parameters').have.lengthOf(1);
 | 
					            should(data[0]).have.property('parameters').have.lengthOf(1);
 | 
				
			||||||
@@ -724,6 +793,20 @@ describe('/template', () => {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					      it('creates a changelog', done => {
 | 
				
			||||||
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					          method: 'post',
 | 
				
			||||||
 | 
					          url: '/template/measurement/new',
 | 
				
			||||||
 | 
					          auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					          httpStatus: 200,
 | 
				
			||||||
 | 
					          req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]},
 | 
				
			||||||
 | 
					          log: {
 | 
				
			||||||
 | 
					            collection: 'measurement_templates',
 | 
				
			||||||
 | 
					            dataAdd: {version: 1},
 | 
				
			||||||
 | 
					            dataIgn: ['first_id']
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      it('rejects a missing name', done => {
 | 
					      it('rejects a missing name', done => {
 | 
				
			||||||
        TestHelper.request(server, done, {
 | 
					        TestHelper.request(server, done, {
 | 
				
			||||||
          method: 'post',
 | 
					          method: 'post',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,8 @@ import ConditionTemplateModel from '../models/condition_template';
 | 
				
			|||||||
import MeasurementTemplateModel from '../models/measurement_template';
 | 
					import MeasurementTemplateModel from '../models/measurement_template';
 | 
				
			||||||
import res400 from './validate/res400';
 | 
					import res400 from './validate/res400';
 | 
				
			||||||
import IdValidate from './validate/id';
 | 
					import IdValidate from './validate/id';
 | 
				
			||||||
 | 
					import mongoose from "mongoose";
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,6 +53,7 @@ router.put('/template/:collection(measurement|condition)/' + IdValidate.paramete
 | 
				
			|||||||
    template.version = templateData.version + 1;  // increase version
 | 
					    template.version = templateData.version + 1;  // increase version
 | 
				
			||||||
    await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {  // save new template, fill with old properties
 | 
					    await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {  // save new template, fill with old properties
 | 
				
			||||||
      if (err) next (err);
 | 
					      if (err) next (err);
 | 
				
			||||||
 | 
					      db.log(req, req.params.collection + '_templates', {_id: data._id}, data.toObject());
 | 
				
			||||||
      res.json(TemplateValidate.output(data.toObject()));
 | 
					      res.json(TemplateValidate.output(data.toObject()));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -65,9 +68,12 @@ router.post('/template/:collection(measurement|condition)/new', async (req, res,
 | 
				
			|||||||
  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.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());
 | 
				
			||||||
    res.json(TemplateValidate.output(data.toObject()));
 | 
					    res.json(TemplateValidate.output(data.toObject()));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import should from 'should/as-function';
 | 
				
			|||||||
import UserModel from '../models/user';
 | 
					import UserModel from '../models/user';
 | 
				
			||||||
import TestHelper from "../test/helper";
 | 
					import TestHelper from "../test/helper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: reject usernames containing admin, etc.
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('/user', () => {
 | 
					describe('/user', () => {
 | 
				
			||||||
  let server;
 | 
					  let server;
 | 
				
			||||||
@@ -200,6 +200,19 @@ describe('/user', () => {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'put',
 | 
				
			||||||
 | 
					        url: '/user',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {name: 'adminnew', email: 'adminnew@bosch.com', pass: 'Abc123##', location: 'Abt', device_name: 'test'},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'users',
 | 
				
			||||||
 | 
					          dataIgn: ['pass']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('lets the admin change a user level', done => {
 | 
					    it('lets the admin change a user level', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'put',
 | 
					        method: 'put',
 | 
				
			||||||
@@ -371,6 +384,17 @@ describe('/user', () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'delete',
 | 
				
			||||||
 | 
					        url: '/user',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'users'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects requests from non-admins for another user', done => {
 | 
					    it('rejects requests from non-admins for another user', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'delete',
 | 
					        method: 'delete',
 | 
				
			||||||
@@ -483,6 +507,19 @@ describe('/user', () => {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/user/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'users',
 | 
				
			||||||
 | 
					          dataIgn: ['pass', 'key']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('rejects a username already in use', done => {
 | 
					    it('rejects a username already in use', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
@@ -588,6 +625,18 @@ describe('/user', () => {
 | 
				
			|||||||
        res: {status: 'OK'}
 | 
					        res: {status: 'OK'}
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('creates a changelog', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'post',
 | 
				
			||||||
 | 
					        url: '/user/passreset',
 | 
				
			||||||
 | 
					        httpStatus: 200,
 | 
				
			||||||
 | 
					        req: {email: 'jane.doe@bosch.com', name: 'janedoe'},
 | 
				
			||||||
 | 
					        log: {
 | 
				
			||||||
 | 
					          collection: 'users',
 | 
				
			||||||
 | 
					          dataIgn: ['email', 'name', 'pass']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('returns 404 for wrong username/email combo', done => {
 | 
					    it('returns 404 for wrong username/email combo', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
        method: 'post',
 | 
					        method: 'post',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import UserValidate from './validate/user';
 | 
				
			|||||||
import UserModel from '../models/user';
 | 
					import UserModel from '../models/user';
 | 
				
			||||||
import mail from '../helpers/mail';
 | 
					import mail from '../helpers/mail';
 | 
				
			||||||
import res400 from './validate/res400';
 | 
					import res400 from './validate/res400';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = express.Router();
 | 
					const router = express.Router();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,7 +54,7 @@ router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => {
 | 
				
			|||||||
    if (!await usernameCheck(user.name, res, next)) return;
 | 
					    if (!await usernameCheck(user.name, res, next)) return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await UserModel.findOneAndUpdate({name: username}, user, {new: true}).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) {
 | 
				
			||||||
      res.json(UserValidate.output(data));
 | 
					      res.json(UserValidate.output(data));
 | 
				
			||||||
@@ -70,7 +71,7 @@ router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  //
 | 
				
			|||||||
  const username = getUsername(req, res);
 | 
					  const username = getUsername(req, res);
 | 
				
			||||||
  if (!username) return;
 | 
					  if (!username) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  UserModel.findOneAndDelete({name: username}).lean().exec(  (err, data:any) => {
 | 
					  UserModel.findOneAndDelete({name: username}).log(req).lean().exec(  (err, data:any) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    if (data) {
 | 
					    if (data) {
 | 
				
			||||||
      res.json({status: 'OK'})
 | 
					      res.json({status: 'OK'})
 | 
				
			||||||
@@ -105,6 +106,7 @@ router.post('/user/new', async (req, res, next) => {
 | 
				
			|||||||
    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());
 | 
				
			||||||
      res.json(UserValidate.output(data.toObject()));
 | 
					      res.json(UserValidate.output(data.toObject()));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -119,7 +121,7 @@ router.post('/user/passreset', (req, res, next) => {
 | 
				
			|||||||
      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}, 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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,6 +70,8 @@ export default class MaterialValidate {  // validate input for material
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  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);
 | 
				
			||||||
 | 
					    data.group = data.group_id.name;
 | 
				
			||||||
 | 
					    data.supplier = data.supplier_id.name;
 | 
				
			||||||
    const {value, error} = Joi.object({
 | 
					    const {value, error} = Joi.object({
 | 
				
			||||||
      _id: IdValidate.get(),
 | 
					      _id: IdValidate.get(),
 | 
				
			||||||
      name: this.material.name,
 | 
					      name: this.material.name,
 | 
				
			||||||
@@ -83,6 +85,16 @@ 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
 | 
				
			||||||
 | 
					    const {value, error} = this.material.group.validate(data, {stripUnknown: true});
 | 
				
			||||||
 | 
					    return error !== undefined? null : value;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static outputSuppliers (data)  {// validate suppliers output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
 | 
					    const {value, error} = this.material.supplier.validate(data, {stripUnknown: true});
 | 
				
			||||||
 | 
					    return error !== undefined? null : value;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static outputV() {  // return output validator
 | 
					  static outputV() {  // return output validator
 | 
				
			||||||
    return Joi.object({
 | 
					    return Joi.object({
 | 
				
			||||||
      _id: IdValidate.get(),
 | 
					      _id: IdValidate.get(),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										50
									
								
								src/routes/validate/root.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/routes/validate/root.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import Joi from '@hapi/joi';
 | 
				
			||||||
 | 
					import IdValidate from './id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class RootValidate {  // validate input for root methods
 | 
				
			||||||
 | 
					  private static changelog = {
 | 
				
			||||||
 | 
					    timestamp: Joi.date()
 | 
				
			||||||
 | 
					      .iso()
 | 
				
			||||||
 | 
					      .min('1970-01-01T00:00:00.000Z'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    page: Joi.number()
 | 
				
			||||||
 | 
					      .integer()
 | 
				
			||||||
 | 
					      .min(0)
 | 
				
			||||||
 | 
					      .default(0),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pagesize: Joi.number()
 | 
				
			||||||
 | 
					      .integer()
 | 
				
			||||||
 | 
					      .min(0)
 | 
				
			||||||
 | 
					      .default(25),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    action: Joi.string(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    collection: Joi.string(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    conditions: Joi.object(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data: Joi.object()
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static changelogParams (data) {
 | 
				
			||||||
 | 
					    return Joi.object({
 | 
				
			||||||
 | 
					      timestamp: this.changelog.timestamp.required(),
 | 
				
			||||||
 | 
					      page: this.changelog.page,
 | 
				
			||||||
 | 
					      pagesize: this.changelog.pagesize
 | 
				
			||||||
 | 
					    }).validate(data);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static changelogOutput (data) {
 | 
				
			||||||
 | 
					    data.date = data._id.getTimestamp();
 | 
				
			||||||
 | 
					    data.collection = data.collectionName;
 | 
				
			||||||
 | 
					    data = IdValidate.stringify(data);
 | 
				
			||||||
 | 
					    const {value, error} = Joi.object({
 | 
				
			||||||
 | 
					      date: this.changelog.timestamp,
 | 
				
			||||||
 | 
					      action: this.changelog.action,
 | 
				
			||||||
 | 
					      collection: this.changelog.collection,
 | 
				
			||||||
 | 
					      conditions: this.changelog.conditions,
 | 
				
			||||||
 | 
					      data: this.changelog.data,
 | 
				
			||||||
 | 
					    }).validate(data, {stripUnknown: true});
 | 
				
			||||||
 | 
					    return error !== undefined? null : value;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -67,6 +67,17 @@ export default class SampleValidate {
 | 
				
			|||||||
        notes: this.sample.notes,
 | 
					        notes: this.sample.notes,
 | 
				
			||||||
      }).validate(data);
 | 
					      }).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 {
 | 
					    else {
 | 
				
			||||||
      return{error: 'No parameter specified!', value: {}};
 | 
					      return{error: 'No parameter specified!', value: {}};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ export default class TemplateValidate {
 | 
				
			|||||||
        Joi.object({
 | 
					        Joi.object({
 | 
				
			||||||
          name: Joi.string()
 | 
					          name: Joi.string()
 | 
				
			||||||
            .max(128)
 | 
					            .max(128)
 | 
				
			||||||
 | 
					            .invalid('condition_template')
 | 
				
			||||||
            .required(),
 | 
					            .required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          range: Joi.object({
 | 
					          range: Joi.object({
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										220
									
								
								src/test/db.json
									
									
									
									
									
								
							
							
						
						
									
										220
									
								
								src/test/db.json
									
									
									
									
									
								
							@@ -59,9 +59,8 @@
 | 
				
			|||||||
        "color": "black",
 | 
					        "color": "black",
 | 
				
			||||||
        "batch": "1653000308",
 | 
					        "batch": "1653000308",
 | 
				
			||||||
        "condition": {
 | 
					        "condition": {
 | 
				
			||||||
          "material": "hot air",
 | 
					          "p1": 44,
 | 
				
			||||||
          "weeks": 5,
 | 
					          "condition_template": {"$oid":"200000000000000000000004"}
 | 
				
			||||||
          "condition_template": {"$oid":"200000000000000000000001"}
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "material_id": {"$oid":"100000000000000000000005"},
 | 
					        "material_id": {"$oid":"100000000000000000000005"},
 | 
				
			||||||
        "note_id": {"$oid":"500000000000000000000003"},
 | 
					        "note_id": {"$oid":"500000000000000000000003"},
 | 
				
			||||||
@@ -149,8 +148,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000001"},
 | 
					        "_id": {"$oid":"100000000000000000000001"},
 | 
				
			||||||
        "name": "Stanyl TW 200 F8",
 | 
					        "name": "Stanyl TW 200 F8",
 | 
				
			||||||
        "supplier": "DSM",
 | 
					        "supplier_id": {"$oid":"110000000000000000000001"},
 | 
				
			||||||
        "group": "PA46",
 | 
					        "group_id": {"$oid":"900000000000000000000001"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 40,
 | 
					        "glass_fiber": 40,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -170,8 +169,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000002"},
 | 
					        "_id": {"$oid":"100000000000000000000002"},
 | 
				
			||||||
        "name": "Ultramid T KR 4355 G7",
 | 
					        "name": "Ultramid T KR 4355 G7",
 | 
				
			||||||
        "supplier": "BASF",
 | 
					        "supplier_id": {"$oid":"110000000000000000000002"},
 | 
				
			||||||
        "group": "PA6/6T",
 | 
					        "group_id": {"$oid":"900000000000000000000002"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 35,
 | 
					        "glass_fiber": 35,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -191,8 +190,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000003"},
 | 
					        "_id": {"$oid":"100000000000000000000003"},
 | 
				
			||||||
        "name": "PA GF 50 black (2706)",
 | 
					        "name": "PA GF 50 black (2706)",
 | 
				
			||||||
        "supplier": "Akro-Plastic",
 | 
					        "supplier_id": {"$oid":"110000000000000000000003"},
 | 
				
			||||||
        "group": "PA66+PA6I/6T",
 | 
					        "group_id": {"$oid":"900000000000000000000003"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 0,
 | 
					        "glass_fiber": 0,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -204,8 +203,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000004"},
 | 
					        "_id": {"$oid":"100000000000000000000004"},
 | 
				
			||||||
        "name": "Schulamid 66 GF 25 H",
 | 
					        "name": "Schulamid 66 GF 25 H",
 | 
				
			||||||
        "supplier": "Schulmann",
 | 
					        "supplier_id": {"$oid":"110000000000000000000004"},
 | 
				
			||||||
        "group": "PA66",
 | 
					        "group_id": {"$oid":"900000000000000000000004"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 25,
 | 
					        "glass_fiber": 25,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -221,8 +220,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000005"},
 | 
					        "_id": {"$oid":"100000000000000000000005"},
 | 
				
			||||||
        "name": "Amodel A 1133 HS",
 | 
					        "name": "Amodel A 1133 HS",
 | 
				
			||||||
        "supplier": "Solvay",
 | 
					        "supplier_id": {"$oid":"110000000000000000000005"},
 | 
				
			||||||
        "group": "PPA",
 | 
					        "group_id": {"$oid":"900000000000000000000005"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 33,
 | 
					        "glass_fiber": 33,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -238,8 +237,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000006"},
 | 
					        "_id": {"$oid":"100000000000000000000006"},
 | 
				
			||||||
        "name": "PK-HM natural (4773)",
 | 
					        "name": "PK-HM natural (4773)",
 | 
				
			||||||
        "supplier": "Akro-Plastic",
 | 
					        "supplier_id": {"$oid":"110000000000000000000003"},
 | 
				
			||||||
        "group": "PK",
 | 
					        "group_id": {"$oid":"900000000000000000000006"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 0,
 | 
					        "glass_fiber": 0,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -255,8 +254,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000007"},
 | 
					        "_id": {"$oid":"100000000000000000000007"},
 | 
				
			||||||
        "name": "Ultramid A4H",
 | 
					        "name": "Ultramid A4H",
 | 
				
			||||||
        "supplier": "BASF",
 | 
					        "supplier_id": {"$oid":"110000000000000000000002"},
 | 
				
			||||||
        "group": "PA66",
 | 
					        "group_id": {"$oid":"900000000000000000000004"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 0,
 | 
					        "glass_fiber": 0,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -272,8 +271,8 @@
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"100000000000000000000008"},
 | 
					        "_id": {"$oid":"100000000000000000000008"},
 | 
				
			||||||
        "name": "Latamid 66 H 2 G 30",
 | 
					        "name": "Latamid 66 H 2 G 30",
 | 
				
			||||||
        "supplier": "LATI",
 | 
					        "supplier_id": {"$oid":"110000000000000000000006"},
 | 
				
			||||||
        "group": "PA66",
 | 
					        "group_id": {"$oid":"900000000000000000000004"},
 | 
				
			||||||
        "mineral": 0,
 | 
					        "mineral": 0,
 | 
				
			||||||
        "glass_fiber": 30,
 | 
					        "glass_fiber": 30,
 | 
				
			||||||
        "carbon_fiber": 0,
 | 
					        "carbon_fiber": 0,
 | 
				
			||||||
@@ -287,6 +286,70 @@
 | 
				
			|||||||
        "__v": 0
 | 
					        "__v": 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					    "material_groups": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"900000000000000000000001"},
 | 
				
			||||||
 | 
					        "name": "PA46",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"900000000000000000000002"},
 | 
				
			||||||
 | 
					        "name": "PA6/6T",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"900000000000000000000003"},
 | 
				
			||||||
 | 
					        "name": "PA66+PA6I/6T",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"900000000000000000000004"},
 | 
				
			||||||
 | 
					        "name": "PA66",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"900000000000000000000005"},
 | 
				
			||||||
 | 
					        "name": "PPA",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"900000000000000000000006"},
 | 
				
			||||||
 | 
					        "name": "PK",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "material_suppliers": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"110000000000000000000001"},
 | 
				
			||||||
 | 
					        "name": "DSM",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"110000000000000000000002"},
 | 
				
			||||||
 | 
					        "name": "BASF",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"110000000000000000000003"},
 | 
				
			||||||
 | 
					        "name": "Akro-Plastic",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"110000000000000000000004"},
 | 
				
			||||||
 | 
					        "name": "Schulmann",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"110000000000000000000005"},
 | 
				
			||||||
 | 
					        "name": "Solvay",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"110000000000000000000006"},
 | 
				
			||||||
 | 
					        "name": "LATI",
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
    "measurements": [
 | 
					    "measurements": [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"800000000000000000000001"},
 | 
					        "_id": {"$oid":"800000000000000000000001"},
 | 
				
			||||||
@@ -343,11 +406,23 @@
 | 
				
			|||||||
        "status": 10,
 | 
					        "status": 10,
 | 
				
			||||||
        "measurement_template": {"$oid":"300000000000000000000002"},
 | 
					        "measurement_template": {"$oid":"300000000000000000000002"},
 | 
				
			||||||
        "__v": 0
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"800000000000000000000006"},
 | 
				
			||||||
 | 
					        "sample_id": {"$oid":"400000000000000000000006"},
 | 
				
			||||||
 | 
					        "values": {
 | 
				
			||||||
 | 
					          "weight %": 0.5,
 | 
				
			||||||
 | 
					          "standard deviation":null
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "status": 0,
 | 
				
			||||||
 | 
					        "measurement_template": {"$oid":"300000000000000000000002"},
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "condition_templates": [
 | 
					    "condition_templates": [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"200000000000000000000001"},
 | 
					        "_id": {"$oid":"200000000000000000000001"},
 | 
				
			||||||
 | 
					        "first_id": {"$oid":"200000000000000000000001"},
 | 
				
			||||||
        "name": "heat treatment",
 | 
					        "name": "heat treatment",
 | 
				
			||||||
        "version": 1,
 | 
					        "version": 1,
 | 
				
			||||||
        "parameters": [
 | 
					        "parameters": [
 | 
				
			||||||
@@ -371,22 +446,37 @@
 | 
				
			|||||||
        "__v": 0
 | 
					        "__v": 0
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"200000000000000000000002"},
 | 
					        "_id": {"$oid":"200000000000000000000003"},
 | 
				
			||||||
        "name": "heat treatment 2",
 | 
					        "first_id": {"$oid":"200000000000000000000003"},
 | 
				
			||||||
        "version": 2,
 | 
					        "name": "raw material",
 | 
				
			||||||
 | 
					        "version": 1,
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"200000000000000000000004"},
 | 
				
			||||||
 | 
					        "first_id": {"$oid":"200000000000000000000004"},
 | 
				
			||||||
 | 
					        "name": "old condition",
 | 
				
			||||||
 | 
					        "version": 1,
 | 
				
			||||||
        "parameters": [
 | 
					        "parameters": [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            "name": "material",
 | 
					            "name": "p1",
 | 
				
			||||||
            "range": {}
 | 
					            "range": {}
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "__v": 0
 | 
					        "__v": 0
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"200000000000000000000003"},
 | 
					        "_id": {"$oid":"200000000000000000000005"},
 | 
				
			||||||
        "name": "raw material",
 | 
					        "first_id": {"$oid":"200000000000000000000004"},
 | 
				
			||||||
        "version": 1,
 | 
					        "name": "new condition",
 | 
				
			||||||
 | 
					        "version": 2,
 | 
				
			||||||
        "parameters": [
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "p11",
 | 
				
			||||||
 | 
					            "range": {}
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "__v": 0
 | 
					        "__v": 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -394,6 +484,7 @@
 | 
				
			|||||||
    "measurement_templates": [
 | 
					    "measurement_templates": [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"300000000000000000000001"},
 | 
					        "_id": {"$oid":"300000000000000000000001"},
 | 
				
			||||||
 | 
					        "first_id": {"$oid":"300000000000000000000001"},
 | 
				
			||||||
        "name": "spectrum",
 | 
					        "name": "spectrum",
 | 
				
			||||||
        "version": 1,
 | 
					        "version": 1,
 | 
				
			||||||
        "parameters": [
 | 
					        "parameters": [
 | 
				
			||||||
@@ -408,8 +499,9 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"300000000000000000000002"},
 | 
					        "_id": {"$oid":"300000000000000000000002"},
 | 
				
			||||||
 | 
					        "first_id": {"$oid":"300000000000000000000002"},
 | 
				
			||||||
        "name": "kf",
 | 
					        "name": "kf",
 | 
				
			||||||
        "version": 2,
 | 
					        "version": 1,
 | 
				
			||||||
        "parameters": [
 | 
					        "parameters": [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            "name": "weight %",
 | 
					            "name": "weight %",
 | 
				
			||||||
@@ -430,6 +522,7 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "_id": {"$oid":"300000000000000000000003"},
 | 
					        "_id": {"$oid":"300000000000000000000003"},
 | 
				
			||||||
 | 
					        "first_id": {"$oid":"300000000000000000000003"},
 | 
				
			||||||
        "name": "mt 3",
 | 
					        "name": "mt 3",
 | 
				
			||||||
        "version": 1,
 | 
					        "version": 1,
 | 
				
			||||||
        "parameters": [
 | 
					        "parameters": [
 | 
				
			||||||
@@ -441,6 +534,21 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "__v": 0
 | 
					        "__v": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id": {"$oid":"300000000000000000000004"},
 | 
				
			||||||
 | 
					        "first_id": {"$oid":"300000000000000000000003"},
 | 
				
			||||||
 | 
					        "name": "mt 31",
 | 
				
			||||||
 | 
					        "version": 2,
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "val2",
 | 
				
			||||||
 | 
					            "range": {
 | 
				
			||||||
 | 
					              "values": [1,2,3,4]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "__v": 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "users": [
 | 
					    "users": [
 | 
				
			||||||
@@ -488,6 +596,64 @@
 | 
				
			|||||||
        "key": "000000000000000000001004",
 | 
					        "key": "000000000000000000001004",
 | 
				
			||||||
        "__v": 0
 | 
					        "__v": 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "changelogs": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id" : {"$oid": "120000010000000000000000"},
 | 
				
			||||||
 | 
					        "action" : "PUT /sample/400000000000000000000001",
 | 
				
			||||||
 | 
					        "collectionName" : "samples",
 | 
				
			||||||
 | 
					        "conditions" : {
 | 
				
			||||||
 | 
					          "_id" : {"$oid": "400000000000000000000001"}
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "data" : {
 | 
				
			||||||
 | 
					          "type" : "part",
 | 
				
			||||||
 | 
					          "status" : 0
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "user_id" : {"$oid": "000000000000000000000003"},
 | 
				
			||||||
 | 
					        "__v" : 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id" : {"$oid": "120000020000000000000000"},
 | 
				
			||||||
 | 
					        "action" : "PUT /sample/400000000000000000000001",
 | 
				
			||||||
 | 
					        "collectionName" : "samples",
 | 
				
			||||||
 | 
					        "conditions" : {
 | 
				
			||||||
 | 
					          "_id" : {"$oid": "400000000000000000000001"}
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "data" : {
 | 
				
			||||||
 | 
					          "type" : "part",
 | 
				
			||||||
 | 
					          "status" : 0
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "user_id" : {"$oid": "000000000000000000000003"},
 | 
				
			||||||
 | 
					        "__v" : 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id" : {"$oid": "120000030000000000000000"},
 | 
				
			||||||
 | 
					        "action" : "PUT /sample/400000000000000000000001",
 | 
				
			||||||
 | 
					        "collectionName" : "samples",
 | 
				
			||||||
 | 
					        "conditions" : {
 | 
				
			||||||
 | 
					          "_id" : {"$oid": "400000000000000000000001"}
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "data" : {
 | 
				
			||||||
 | 
					          "type" : "part",
 | 
				
			||||||
 | 
					          "status" : 0
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "user_id" : {"$oid": "000000000000000000000003"},
 | 
				
			||||||
 | 
					        "__v" : 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "_id" : {"$oid": "120000040000000000000000"},
 | 
				
			||||||
 | 
					        "action" : "PUT /sample/400000000000000000000001",
 | 
				
			||||||
 | 
					        "collectionName" : "samples",
 | 
				
			||||||
 | 
					        "conditions" : {
 | 
				
			||||||
 | 
					          "_id" : {"$oid": "400000000000000000000001"}
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "data" : {
 | 
				
			||||||
 | 
					          "type" : "part",
 | 
				
			||||||
 | 
					          "status" : 0
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "user_id" : {"$oid": "000000000000000000000003"},
 | 
				
			||||||
 | 
					        "__v" : 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,14 +1,17 @@
 | 
				
			|||||||
import supertest from 'supertest';
 | 
					import supertest from 'supertest';
 | 
				
			||||||
import should from 'should/as-function';
 | 
					import should from 'should/as-function';
 | 
				
			||||||
import db from "../db";
 | 
					import _ from 'lodash';
 | 
				
			||||||
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					import ChangelogModel from '../models/changelog';
 | 
				
			||||||
 | 
					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'},
 | 
					    admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'},
 | 
				
			||||||
    janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002'},
 | 
					    janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'},
 | 
				
			||||||
    user: {pass: 'Xyz890*)', key: '000000000000000000001001'},
 | 
					    user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'},
 | 
				
			||||||
    johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'}
 | 
					    johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'}
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public static res = {  // default responses
 | 
					  public static res = {  // default responses
 | 
				
			||||||
@@ -92,6 +95,35 @@ export default class TestHelper {
 | 
				
			|||||||
        done();
 | 
					        done();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    else if (options.hasOwnProperty('log')) {  // check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)}
 | 
				
			||||||
 | 
					      return st.end(err => {
 | 
				
			||||||
 | 
					        if (err) return done (err);
 | 
				
			||||||
 | 
					        ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0).lean().exec((err, data) => {  // latest entry
 | 
				
			||||||
 | 
					          if (err) return done(err);
 | 
				
			||||||
 | 
					          should(data).have.only.keys('_id', 'action', 'collectionName', 'conditions', 'data', 'user_id', '__v');
 | 
				
			||||||
 | 
					          should(data).have.property('action', options.method.toUpperCase() + ' ' + options.url);
 | 
				
			||||||
 | 
					          should(data).have.property('collectionName', options.log.collection);
 | 
				
			||||||
 | 
					          if (options.log.hasOwnProperty('data')) {
 | 
				
			||||||
 | 
					            should(data).have.property('data', options.log.data);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          else {
 | 
				
			||||||
 | 
					            const ignore = ['_id', '__v'];
 | 
				
			||||||
 | 
					            if (options.log.hasOwnProperty('dataIgn')) {
 | 
				
			||||||
 | 
					              ignore.push(...options.log.dataIgn);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            let tmp = options.req ? options.req : {};
 | 
				
			||||||
 | 
					            if (options.log.hasOwnProperty('dataAdd')) {
 | 
				
			||||||
 | 
					              _.assign(tmp, options.log.dataAdd)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            should(IdValidate.stringify(_.omit(data.data, ignore))).be.eql(_.omit(tmp, ignore));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (data.user_id) {
 | 
				
			||||||
 | 
					            should(data.user_id.toString()).be.eql(this.auth[options.auth.basic].id);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          done();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    else {  // return object to do .end() manually
 | 
					    else {  // return object to do .end() manually
 | 
				
			||||||
      return st;
 | 
					      return st;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user