Merge pull request #2 in ~VLE2FE/dfop-api from user to develop
* commit '20f57acd2aa031a3fbce7b4f61f6a64749d98606': implemented first /sample methods finished /template methods finished /material methods styled swagger added /materials route added custom type definitions added /user DELETE route added /user/key and edited /user regex added test helper and rewrote tests added PUT /user route added GET /user route changed to findById and improved db.loadJson added passreset and mail helper added authorization cannot add username twice implemented first tests and basic functionality
This commit is contained in:
commit
c407da2fbc
1
.gitignore
vendored
1
.gitignore
vendored
@ -112,3 +112,4 @@ dist
|
||||
**/.idea/tasks.xml
|
||||
**/.idea/shelf
|
||||
**/.idea/*.iml
|
||||
/tmp/
|
||||
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
9
.idea/dictionaries/VLE2FE.xml
Normal file
9
.idea/dictionaries/VLE2FE.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="VLE2FE">
|
||||
<words>
|
||||
<w>bcrypt</w>
|
||||
<w>cfenv</w>
|
||||
<w>dfopdb</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
@ -2,5 +2,6 @@
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="JSUnfilteredForInLoop" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ReservedWordUsedAsNameJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
@ -6,7 +6,10 @@ info:
|
||||
version: 1.0.0
|
||||
description: |
|
||||
This API gives access to the project database.<br>
|
||||
Access is restricted. Authentication can be obtained with HTTP Basic Auth using username and password. Data access methods can also be accessed using an API key at the URL ending like ?key=xxx<br>
|
||||
Access is restricted. Authentication can be obtained with HTTP Basic Auth using username and password.
|
||||
Data access methods can also be accessed using an API key at the URL ending like ?key=xxx<br>
|
||||
The description lists available authentication methods, also the locks of each method close correspondingly
|
||||
if the entered authentication is allowed.<br><br>
|
||||
There are a number of different user levels: <br>
|
||||
<ul>
|
||||
<li>read: read access to the samples database</li>
|
||||
@ -15,6 +18,15 @@ info:
|
||||
<li>dev: handling machine learning models</li>
|
||||
<li>admin: user administration</li>
|
||||
</ul>
|
||||
Password policy:
|
||||
<ul>
|
||||
<li>at least one digit</li>
|
||||
<li>at least one lower case letter</li>
|
||||
<li>at least one upper case letter</li>
|
||||
<li>at least one of the following special characters: !"#%&'()*+,-./:;<=>?@[\]^_`{|}~</li>
|
||||
<li>no whitespace</li>
|
||||
<li>at least 8 characters</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
@ -36,7 +48,7 @@ tags:
|
||||
- name: /material
|
||||
- name: /condition
|
||||
- name: /measurement
|
||||
- name: /templates
|
||||
- name: /template
|
||||
- name: /model
|
||||
- name: /user
|
||||
|
73
api/condition.yaml
Normal file
73
api/condition.yaml
Normal file
@ -0,0 +1,73 @@
|
||||
/condition/{id}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO condition by id
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /condition
|
||||
responses:
|
||||
200:
|
||||
description: condition details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Condition'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'api.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change condition
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /condition
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Condition'
|
||||
responses:
|
||||
200:
|
||||
description: condition details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Condition'
|
||||
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'
|
||||
delete:
|
||||
summary: TODO delete condition
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /condition
|
||||
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'
|
119
api/material.yaml
Normal file
119
api/material.yaml
Normal file
@ -0,0 +1,119 @@
|
||||
/materials:
|
||||
get:
|
||||
summary: lists all materials
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /material
|
||||
responses:
|
||||
200:
|
||||
description: all material details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
|
||||
/material/{id}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: get material details
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /material
|
||||
responses:
|
||||
200:
|
||||
description: material details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'api.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: change material
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /material
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
responses:
|
||||
200:
|
||||
description: material details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
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'
|
||||
delete:
|
||||
summary: delete material
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
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:
|
||||
post:
|
||||
summary: add material
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /material
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
responses:
|
||||
200:
|
||||
description: material details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'api.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
73
api/measurement.yaml
Normal file
73
api/measurement.yaml
Normal file
@ -0,0 +1,73 @@
|
||||
/measurement/{id}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO measurement values by id
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /measurement
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Measurement'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'api.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change measurement
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /measurement
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Measurement'
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Measurement'
|
||||
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'
|
||||
delete:
|
||||
summary: TODO delete measurement
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /measurement
|
||||
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'
|
70
api/model.yaml
Normal file
70
api/model.yaml
Normal file
@ -0,0 +1,70 @@
|
||||
/model/{name}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: TODO get model data by name
|
||||
description: 'Auth: all, levels: dev, admin'
|
||||
tags:
|
||||
- /model
|
||||
responses:
|
||||
200:
|
||||
description: binary model data
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
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'
|
||||
put:
|
||||
summary: TODO add/replace model data by name
|
||||
description: 'Auth: all, levels: dev, admin'
|
||||
tags:
|
||||
- /model
|
||||
requestBody:
|
||||
required: true
|
||||
description: binary model data
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
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'
|
||||
delete:
|
||||
summary: TODO delete model data
|
||||
description: 'Auth: basic, levels: dev, admin'
|
||||
tags:
|
||||
- /model
|
||||
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'
|
43
api/others.yaml
Normal file
43
api/others.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
/:
|
||||
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'
|
@ -4,8 +4,10 @@ Id:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 5ea0450ed851c30a90e70894
|
||||
Name:
|
||||
name: name
|
||||
description: has to be URL encoded
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
145
api/sample.yaml
Normal file
145
api/sample.yaml
Normal file
@ -0,0 +1,145 @@
|
||||
/samples:
|
||||
get:
|
||||
summary: all samples in overview
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
description: samples overview
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/SampleRefs'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
/sample/{id}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO sample details
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
description: samples details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/SampleDetail'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'api.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO change sample
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Sample'
|
||||
responses:
|
||||
200:
|
||||
description: samples details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/SampleDetail'
|
||||
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'
|
||||
delete:
|
||||
summary: TODO delete sample
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
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:
|
||||
post:
|
||||
summary: add sample
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/Sample'
|
||||
responses:
|
||||
200:
|
||||
description: samples details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/SampleRefs'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'api.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
|
||||
/sample/notes/fields:
|
||||
get:
|
||||
summary: TODO list all existing field names for custom notes fields
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
description: field names and quantity of usage
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
qty:
|
||||
type: number
|
||||
example: 20
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
183
api/schemas.yaml
Normal file
183
api/schemas.yaml
Normal file
@ -0,0 +1,183 @@
|
||||
Id:
|
||||
type: string
|
||||
example: 5ea0450ed851c30a90e70894
|
||||
_Id:
|
||||
properties:
|
||||
_id:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Id'
|
||||
readOnly: true
|
||||
Color:
|
||||
properties:
|
||||
color:
|
||||
type: string
|
||||
example: black
|
||||
SampleProperties:
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
example: Rng172
|
||||
type:
|
||||
type: string
|
||||
example: granulate
|
||||
batch:
|
||||
type: string
|
||||
example: 1560237365
|
||||
|
||||
SampleRefs:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
- $ref: 'api.yaml#/components/schemas/Color'
|
||||
- $ref: 'api.yaml#/components/schemas/SampleProperties'
|
||||
properties:
|
||||
material_id:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
note_id:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
user_id:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
Sample:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
- $ref: 'api.yaml#/components/schemas/Color'
|
||||
- $ref: 'api.yaml#/components/schemas/SampleProperties'
|
||||
properties:
|
||||
material_id:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Id'
|
||||
notes:
|
||||
type: object
|
||||
properties:
|
||||
comment:
|
||||
type: string
|
||||
sample_references:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
id:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
relation:
|
||||
type: string
|
||||
example: part to this sample
|
||||
SampleDetail:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
- $ref: 'api.yaml#/components/schemas/Color'
|
||||
- $ref: 'api.yaml#/components/schemas/SampleProperties'
|
||||
properties:
|
||||
material:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
notes:
|
||||
type: object
|
||||
properties:
|
||||
comment:
|
||||
type: string
|
||||
sample_references:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
conditions:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/Condition'
|
||||
|
||||
Material:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: Stanyl TW 200 F8
|
||||
supplier:
|
||||
type: string
|
||||
example: DSM
|
||||
group:
|
||||
type: string
|
||||
example: PA46
|
||||
mineral:
|
||||
type: number
|
||||
example: 0
|
||||
glass_fiber:
|
||||
type: number
|
||||
example: 40
|
||||
carbon_fiber:
|
||||
type: number
|
||||
example: 0
|
||||
numbers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Color'
|
||||
properties:
|
||||
number:
|
||||
type: number
|
||||
example: 5514263423
|
||||
|
||||
Condition:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
sample_id:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
parameters:
|
||||
type: object
|
||||
treatment_template:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
|
||||
Measurement:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
condition_id:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
values:
|
||||
type: object
|
||||
measurement_template:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
|
||||
Template:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
parameters:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
range:
|
||||
type: object
|
||||
|
||||
Email:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
example: john.doe@bosch.com
|
||||
UserName:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: johndoe
|
||||
User:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
- $ref: 'api.yaml#/components/schemas/UserName'
|
||||
- $ref: 'api.yaml#/components/schemas/Email'
|
||||
properties:
|
||||
pass:
|
||||
type: string
|
||||
writeOnly: true
|
||||
example: Abc123!#
|
||||
level:
|
||||
type: string
|
||||
example: read
|
||||
location:
|
||||
type: string
|
||||
example: Rng
|
||||
device_name:
|
||||
type: string
|
||||
example: Alpha II
|
263
api/template.yaml
Normal file
263
api/template.yaml
Normal file
@ -0,0 +1,263 @@
|
||||
/template/treatments:
|
||||
get:
|
||||
summary: all available treatment methods
|
||||
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /template
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: list of treatments
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
_id: 5ea0450ed851c30a90e70894
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
values:
|
||||
- copper
|
||||
- hot air
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
/template/treatment/{name}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: treatment method details
|
||||
description: 'Auth: basic, levels: read, write, maintain, admin'
|
||||
tags:
|
||||
- /template
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: treatment details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
_id: 5ea0450ed851c30a90e70894
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
values:
|
||||
- copper
|
||||
- hot air
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'api.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: add/change treatment method
|
||||
description: 'Auth: basic, levels: maintain, admin'
|
||||
tags:
|
||||
- /template
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
values:
|
||||
- copper
|
||||
- hot air
|
||||
responses:
|
||||
200:
|
||||
description: treatment details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
_id: 5ea0450ed851c30a90e70894
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
values:
|
||||
- copper
|
||||
- hot air
|
||||
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'
|
||||
delete:
|
||||
summary: delete treatment method
|
||||
description: 'Auth: basic, levels: maintain, admin'
|
||||
tags:
|
||||
- /template
|
||||
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'
|
||||
/template/measurements:
|
||||
get:
|
||||
summary: all available measurement methods
|
||||
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /template
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: list of measurement methods
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
_id: 5ea0450ed851c30a90e70894
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
/template/measurement/{name}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: measurement method details
|
||||
description: 'Auth: basic, levels: read, write, maintain, admin'
|
||||
tags:
|
||||
- /template
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
_id: 5ea0450ed851c30a90e70894
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'api.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: add/change measurement method
|
||||
description: 'Auth: basic, levels: maintain, admin'
|
||||
tags:
|
||||
- /template
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
_id: 5ea0450ed851c30a90e70894
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/Template'
|
||||
example:
|
||||
_id: 5ea0450ed851c30a90e70894
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
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'
|
||||
delete:
|
||||
summary: delete measurement method
|
||||
description: 'Auth: basic, levels: maintain, admin'
|
||||
tags:
|
||||
- /template
|
||||
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'
|
255
api/user.yaml
Normal file
255
api/user.yaml
Normal file
@ -0,0 +1,255 @@
|
||||
/users:
|
||||
get:
|
||||
summary: lists all users
|
||||
description: 'Auth: basic, levels: admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: user API key
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/User'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'api.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
/user:
|
||||
get:
|
||||
summary: list own user details
|
||||
description: 'Auth: basic, levels: read, write, maintain, admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/User'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'api.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: change user details
|
||||
description: 'Auth: basic, levels: read, write, maintain, admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
- $ref: 'api.yaml#/components/schemas/UserName'
|
||||
- $ref: 'api.yaml#/components/schemas/Email'
|
||||
properties:
|
||||
pass:
|
||||
type: string
|
||||
writeOnly: true
|
||||
example: Abc123!#
|
||||
location:
|
||||
type: string
|
||||
example: Rng
|
||||
device_name:
|
||||
type: string
|
||||
example: Alpha II
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/User'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'api.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: delete user
|
||||
description: 'Auth: basic, levels: read, write, maintain, admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
$ref: 'api.yaml#/components/responses/Ok'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
/user/{name}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: list user details
|
||||
description: 'Auth: basic, levels: admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/User'
|
||||
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'
|
||||
put:
|
||||
summary: change user details
|
||||
description: 'Auth: basic, levels: admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/User'
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/User'
|
||||
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'
|
||||
delete:
|
||||
summary: delete user
|
||||
description: 'Auth: basic, levels: admin'
|
||||
tags:
|
||||
- /user
|
||||
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'
|
||||
/user/key:
|
||||
get:
|
||||
summary: get API key for the user
|
||||
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
example: 5ea0450ed851c30a90e70899
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
/user/new:
|
||||
post:
|
||||
summary: add new user
|
||||
description: 'Auth: basic, levels: admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- email
|
||||
- name
|
||||
- pass
|
||||
- level
|
||||
- location
|
||||
- device_name
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/User'
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/User'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'api.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
/user/passreset:
|
||||
post:
|
||||
summary: reset password and send mail to restore
|
||||
description: 'Auth: none'
|
||||
tags:
|
||||
- /user
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
description: mail saved in user profile to provide authentication
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/UserName'
|
||||
- $ref: 'api.yaml#/components/schemas/Email'
|
||||
responses:
|
||||
200:
|
||||
$ref: 'api.yaml#/components/responses/Ok'
|
||||
404:
|
||||
$ref: 'api.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
@ -1,69 +0,0 @@
|
||||
/condition/{id}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO condition by id
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /condition
|
||||
responses:
|
||||
200:
|
||||
description: condition details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Condition'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change condition
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /condition
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Condition'
|
||||
responses:
|
||||
200:
|
||||
description: condition details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Condition'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete condition
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /condition
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
@ -1,63 +0,0 @@
|
||||
/material/{id}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO get material details
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /material
|
||||
responses:
|
||||
200:
|
||||
description: created material
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Material'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change material
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /material
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Material'
|
||||
responses:
|
||||
200:
|
||||
description: material details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Material'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete material
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /material
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
@ -1,69 +0,0 @@
|
||||
/measurement/{id}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO measurement values by id
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /measurement
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Measurement'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change measurement
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /measurement
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Measurement'
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Measurement'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete measurement
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /measurement
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
@ -1,68 +0,0 @@
|
||||
/model/{name}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: TODO get model data by name
|
||||
description: 'levels: dev, admin'
|
||||
tags:
|
||||
- /model
|
||||
responses:
|
||||
200:
|
||||
description: binary model data
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/replace model data by name
|
||||
description: 'levels: dev, admin'
|
||||
tags:
|
||||
- /model
|
||||
requestBody:
|
||||
required: true
|
||||
description: binary model data
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete model data
|
||||
description: 'levels: dev, admin'
|
||||
tags:
|
||||
- /model
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
@ -1,18 +0,0 @@
|
||||
/:
|
||||
get:
|
||||
summary: Root method
|
||||
tags:
|
||||
- /
|
||||
security: []
|
||||
responses:
|
||||
200:
|
||||
description: Server is working
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example: 'API server up and running!'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
108
oas/sample.yaml
108
oas/sample.yaml
@ -1,108 +0,0 @@
|
||||
/samples:
|
||||
get:
|
||||
summary: TODO all samples in overview
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
description: samples overview
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Samples'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/sample/{id}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO sample details
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
description: samples details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/SampleDetail'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change sample
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Sample'
|
||||
responses:
|
||||
200:
|
||||
description: samples details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/SampleDetail'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete sample
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/sample/notes/fields:
|
||||
get:
|
||||
summary: TODO list all existing field names for custom notes fields
|
||||
description: 'levels: write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
description: field names and quantity of usage
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
qty:
|
||||
type: number
|
||||
example: 20
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
164
oas/schemas.yaml
164
oas/schemas.yaml
@ -1,164 +0,0 @@
|
||||
Id:
|
||||
type: string
|
||||
_Id:
|
||||
properties:
|
||||
_id:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Id'
|
||||
readOnly: true
|
||||
Color:
|
||||
properties:
|
||||
color:
|
||||
type: string
|
||||
SampleProperties:
|
||||
properties:
|
||||
sample_number:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
batch:
|
||||
type: string
|
||||
validated:
|
||||
type: boolean
|
||||
|
||||
Samples:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
- $ref: 'oas.yaml#/components/schemas/Color'
|
||||
- $ref: 'oas.yaml#/components/schemas/SampleProperties'
|
||||
properties:
|
||||
material_id:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
note_id:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
user_id:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
Sample:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
- $ref: 'oas.yaml#/components/schemas/Color'
|
||||
- $ref: 'oas.yaml#/components/schemas/SampleProperties'
|
||||
properties:
|
||||
material:
|
||||
$ref: 'oas.yaml#/components/schemas/Material'
|
||||
notes:
|
||||
type: object
|
||||
properties:
|
||||
comments:
|
||||
type: string
|
||||
sample_references:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
SampleDetail:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
- $ref: 'oas.yaml#/components/schemas/Color'
|
||||
- $ref: 'oas.yaml#/components/schemas/SampleProperties'
|
||||
properties:
|
||||
material:
|
||||
$ref: 'oas.yaml#/components/schemas/Material'
|
||||
notes:
|
||||
type: object
|
||||
properties:
|
||||
comments:
|
||||
type: string
|
||||
sample_references:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
conditions:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/Condition'
|
||||
|
||||
Material:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
material_numbers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Color'
|
||||
properties:
|
||||
number:
|
||||
type: number
|
||||
material_group:
|
||||
type: string
|
||||
supplier:
|
||||
type: string
|
||||
material_name:
|
||||
type: string
|
||||
mineral:
|
||||
type: number
|
||||
glass_fiber:
|
||||
type: number
|
||||
carbon_fiber:
|
||||
type: number
|
||||
|
||||
Condition:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
sample_id:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
parameters:
|
||||
type: object
|
||||
treatment_template:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
|
||||
Measurement:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
condition_id:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
values:
|
||||
type: object
|
||||
measurement_template:
|
||||
$ref: 'oas.yaml#/components/schemas/Id'
|
||||
|
||||
Template:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
parameters:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
range:
|
||||
type: object
|
||||
|
||||
Email:
|
||||
required:
|
||||
- email
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
example: john.doe@bosch.com
|
||||
User:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/_Id'
|
||||
- $ref: 'oas.yaml#/components/schemas/Email'
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: johndoe
|
||||
levels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: read
|
||||
location:
|
||||
type: string
|
||||
example: Rng
|
||||
device_name:
|
||||
type: string
|
||||
example: Alpha II
|
@ -1,242 +0,0 @@
|
||||
/template/treatments:
|
||||
get:
|
||||
summary: TODO all available treatment methods
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /templates
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: list of treatments
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
- copper
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/templates/treatment/{name}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: TODO treatment method details
|
||||
description: 'levels: read, write, maintain, admin'
|
||||
tags:
|
||||
- /templates
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: treatment details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
- copper
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change treatment method
|
||||
description: 'levels: maintain, admin'
|
||||
tags:
|
||||
- /templates
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
- copper
|
||||
responses:
|
||||
200:
|
||||
description: treatment details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: heat aging
|
||||
parameters:
|
||||
- name: method
|
||||
range:
|
||||
- copper
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete treatment method
|
||||
description: 'levels: maintain, admin'
|
||||
tags:
|
||||
- /templates
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/template/measurements:
|
||||
get:
|
||||
summary: TODO all available measurement methods
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /templates
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: list of measurement methods
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/templates/measurement/{name}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: TODO measurement method details
|
||||
description: 'levels: read, write, maintain, admin'
|
||||
tags:
|
||||
- /templates
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO add/change measurement method
|
||||
description: 'levels: maintain, admin'
|
||||
tags:
|
||||
- /templates
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
responses:
|
||||
200:
|
||||
description: measurement details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'oas.yaml#/components/schemas/Template'
|
||||
example:
|
||||
name: humidity
|
||||
parameters:
|
||||
- name: kf
|
||||
range:
|
||||
min: 0
|
||||
max: 2
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete measurement method
|
||||
description: 'levels: maintain, admin'
|
||||
tags:
|
||||
- /templates
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
170
oas/user.yaml
170
oas/user.yaml
@ -1,170 +0,0 @@
|
||||
/users:
|
||||
get:
|
||||
summary: TODO lists all users
|
||||
description: 'levels: admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: user API key
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/User'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/user/{name}:
|
||||
parameters:
|
||||
- $ref: 'oas.yaml#/components/parameters/Name'
|
||||
get:
|
||||
summary: TODO list user details
|
||||
description: 'levels: read, write, maintain, dev get their own information without a name property specified, level: admin can get any user using the name parameter'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/User'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO change user details
|
||||
description: 'levels: read, write, maintain, dev can change their own information (except level) without a name property specified, level: admin can change any user using the name parameter'
|
||||
tags:
|
||||
- /user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/User'
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/User'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete user
|
||||
description: 'levels: read, write, maintain, dev can delete their own account, level: admin can delete any user using the name parameter'
|
||||
tags:
|
||||
- /user
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
404:
|
||||
$ref: 'oas.yaml#/components/responses/404'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/user/key:
|
||||
get:
|
||||
summary: TODO get API key for the user
|
||||
description: 'levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/User'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/user/new:
|
||||
post:
|
||||
summary: TODO add new user
|
||||
description: 'levels: admin'
|
||||
tags:
|
||||
- /user
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/User'
|
||||
responses:
|
||||
200:
|
||||
description: user details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'oas.yaml#/components/schemas/User'
|
||||
400:
|
||||
$ref: 'oas.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
403:
|
||||
$ref: 'oas.yaml#/components/responses/403'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
||||
/user/passreset:
|
||||
post:
|
||||
summary: TODO reset password and send mail to restore
|
||||
tags:
|
||||
- /user
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
description: mail saved in user profile to provide authentication
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'oas.yaml#/components/schemas/Email'
|
||||
responses:
|
||||
200:
|
||||
$ref: 'oas.yaml#/components/responses/Ok'
|
||||
401:
|
||||
$ref: 'oas.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'oas.yaml#/components/responses/500'
|
284
package-lock.json
generated
284
package-lock.json
generated
@ -32,6 +32,49 @@
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@hapi/address": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz",
|
||||
"integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==",
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@hapi/formula": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz",
|
||||
"integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A=="
|
||||
},
|
||||
"@hapi/hoek": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz",
|
||||
"integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw=="
|
||||
},
|
||||
"@hapi/joi": {
|
||||
"version": "17.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz",
|
||||
"integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==",
|
||||
"requires": {
|
||||
"@hapi/address": "^4.0.1",
|
||||
"@hapi/formula": "^2.0.0",
|
||||
"@hapi/hoek": "^9.0.0",
|
||||
"@hapi/pinpoint": "^2.0.0",
|
||||
"@hapi/topo": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"@hapi/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw=="
|
||||
},
|
||||
"@hapi/topo": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz",
|
||||
"integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==",
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@jsdevtools/ono": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz",
|
||||
@ -50,21 +93,102 @@
|
||||
"defer-to-connect": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@types/bcrypt": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz",
|
||||
"integrity": "sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ=="
|
||||
},
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/bson": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.2.tgz",
|
||||
"integrity": "sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
|
||||
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/express-serve-static-core": {
|
||||
"version": "4.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.5.tgz",
|
||||
"integrity": "sha512-578YH5Lt88AKoADy0b2jQGwJtrBxezXtVe/MBqWXKZpqx91SnC0pVkVCcxcytz3lWW+cHBYDi3Ysh0WXc+rAYw==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
|
||||
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
|
||||
},
|
||||
"@types/mocha": {
|
||||
"version": "5.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
|
||||
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ=="
|
||||
},
|
||||
"@types/mongodb": {
|
||||
"version": "3.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.10.tgz",
|
||||
"integrity": "sha512-6NkJNfFdFa/njBvN/9eAfq78bWUnapkdR3JbWGGpd7U71PjgKweA4Tlag8psi2mqm973vBYVTD1oc1u0lzRcig==",
|
||||
"requires": {
|
||||
"@types/bson": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/mongoose": {
|
||||
"version": "5.7.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.7.12.tgz",
|
||||
"integrity": "sha512-yzLJk3cdSwuMXaIacUCWUb8m960YcgnID7S4ZPOOgzT39aSC46670TuunN+ajDio7OUcGG4mGg8eOGs2Z6VmrA==",
|
||||
"requires": {
|
||||
"@types/mongodb": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "13.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.6.tgz",
|
||||
"integrity": "sha512-Jg1F+bmxcpENHP23sVKkNuU3uaxPnsBMW0cLjleiikFKomJQbsn0Cqk2yDvQArqzZN6ABfBkZ0To7pQ8sLdWDg=="
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz",
|
||||
"integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw=="
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
|
||||
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
|
||||
"requires": {
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/mime": "*"
|
||||
}
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@ -160,11 +284,32 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"bcryptjs": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
||||
@ -514,6 +659,11 @@
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"content-filter": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/content-filter/-/content-filter-1.1.2.tgz",
|
||||
"integrity": "sha512-VaZ4Y7h776r0v2WxWqu3iatjYI6/N0msXK8O1ymtkFWbSvaFoCePksS8U60BS6dUMZeAlqhN09SuM7ghdzRP1Q=="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
@ -645,9 +795,9 @@
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0.tgz",
|
||||
"integrity": "sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug==",
|
||||
"version": "1.17.5",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
|
||||
"integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-to-primitive": "^1.2.1",
|
||||
@ -787,6 +937,24 @@
|
||||
"is-buffer": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
|
||||
@ -1181,12 +1349,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
||||
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
|
||||
"integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.0.1"
|
||||
"chalk": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"lowercase-keys": {
|
||||
@ -1275,9 +1443,9 @@
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-7.0.0.tgz",
|
||||
"integrity": "sha512-CirsOPbO3jU86YKjjMzFLcXIb5YiGLUrjrXFHoJ3e2z9vWiaZVCZQ2+gtRGMPWF+nFhN6AWwLM/juzAQ6KRkbA==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz",
|
||||
"integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-colors": "3.2.3",
|
||||
@ -1291,9 +1459,9 @@
|
||||
"growl": "1.10.5",
|
||||
"he": "1.2.0",
|
||||
"js-yaml": "3.13.1",
|
||||
"log-symbols": "2.2.0",
|
||||
"log-symbols": "3.0.0",
|
||||
"minimatch": "3.0.4",
|
||||
"mkdirp": "0.5.1",
|
||||
"mkdirp": "0.5.5",
|
||||
"ms": "2.1.1",
|
||||
"node-environment-flags": "1.0.6",
|
||||
"object.assign": "4.1.0",
|
||||
@ -1301,8 +1469,8 @@
|
||||
"supports-color": "6.0.0",
|
||||
"which": "1.3.1",
|
||||
"wide-align": "1.1.3",
|
||||
"yargs": "13.3.0",
|
||||
"yargs-parser": "13.1.1",
|
||||
"yargs": "13.3.2",
|
||||
"yargs-parser": "13.1.2",
|
||||
"yargs-unparser": "1.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -1351,21 +1519,6 @@
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
@ -1392,6 +1545,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongo-sanitize": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mongo-sanitize/-/mongo-sanitize-1.1.0.tgz",
|
||||
"integrity": "sha512-6gB9AiJD+om2eZLxaPKIP5Q8P3Fr+s+17rVWso7hU0+MAzmIvIMlgTYuyvalDLTtE/p0gczcvJ8A3pbN1XmQ/A=="
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.4.1.tgz",
|
||||
@ -1586,9 +1744,9 @@
|
||||
"integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw=="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
|
||||
"integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-try": "^2.0.0"
|
||||
@ -2009,24 +2167,46 @@
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimleft": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
|
||||
"integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
|
||||
"string.prototype.trimend": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
|
||||
"integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"define-properties": "^1.1.3",
|
||||
"function-bind": "^1.1.1"
|
||||
"es-abstract": "^1.17.5"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimleft": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
|
||||
"integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.17.5",
|
||||
"string.prototype.trimstart": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimright": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
|
||||
"integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
|
||||
"integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"define-properties": "^1.1.3",
|
||||
"function-bind": "^1.1.1"
|
||||
"es-abstract": "^1.17.5",
|
||||
"string.prototype.trimend": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimstart": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
|
||||
"integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.17.5"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
@ -2459,9 +2639,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
|
||||
"integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^5.0.0",
|
||||
@ -2473,7 +2653,7 @@
|
||||
"string-width": "^3.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^13.1.1"
|
||||
"yargs-parser": "^13.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
@ -2505,21 +2685,13 @@
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
|
||||
"integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"yargs-unparser": {
|
||||
|
18
package.json
18
package.json
@ -4,8 +4,9 @@
|
||||
"description": "API for the digital fingerprint of plastics mongodb",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
"test": "mocha dist/**/**.spec.js",
|
||||
"start": "tsc && node dist/index.js",
|
||||
"start": "tsc && node dist/index.js || exit 1",
|
||||
"dev": "nodemon -e ts,yaml --exec \"npm run start\""
|
||||
},
|
||||
"keywords": [],
|
||||
@ -13,11 +14,24 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "^8.0.0",
|
||||
"@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",
|
||||
"basic-auth": "^2.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.19.0",
|
||||
"cfenv": "^1.2.2",
|
||||
"content-filter": "^1.1.2",
|
||||
"express": "^4.17.1",
|
||||
"json-schema": "^0.2.5",
|
||||
"mongo-sanitize": "^1.1.0",
|
||||
"mongoose": "^5.8.7",
|
||||
"nodemon": "^2.0.3",
|
||||
"swagger-ui-express": "^4.1.2",
|
||||
@ -25,7 +39,7 @@
|
||||
"typescript": "^3.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^7.0.0",
|
||||
"mocha": "^7.1.2",
|
||||
"should": "^13.2.3",
|
||||
"supertest": "^4.0.2"
|
||||
}
|
||||
|
116
src/customTypes/express.ts
Normal file
116
src/customTypes/express.ts
Normal file
@ -0,0 +1,116 @@
|
||||
// Type definitions for Express 4.17
|
||||
// Project: http://expressjs.com
|
||||
// Definitions by: Boris Yankov <https://github.com/borisyankov>
|
||||
// China Medical University Hospital <https://github.com/CMUH>
|
||||
// Puneet Arora <https://github.com/puneetar>
|
||||
// Dylan Frankland <https://github.com/dfrankland>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.3
|
||||
|
||||
/* =================== USAGE ===================
|
||||
|
||||
import * as express from "express";
|
||||
var app = express();
|
||||
|
||||
=============================================== */
|
||||
|
||||
/// <reference types="express-serve-static-core" />
|
||||
/// <reference types="serve-static" />
|
||||
|
||||
import * as bodyParser from "body-parser";
|
||||
import serveStatic = require("serve-static");
|
||||
import * as core from "express-serve-static-core";
|
||||
import * as qs from "qs";
|
||||
|
||||
/**
|
||||
* Creates an Express application. The express() function is a top-level function exported by the express module.
|
||||
*/
|
||||
declare function e(): core.Express;
|
||||
|
||||
declare namespace e {
|
||||
/**
|
||||
* This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser.
|
||||
* @since 4.16.0
|
||||
*/
|
||||
var json: typeof bodyParser.json;
|
||||
|
||||
/**
|
||||
* This is a built-in middleware function in Express. It parses incoming requests with Buffer payloads and is based on body-parser.
|
||||
* @since 4.17.0
|
||||
*/
|
||||
var raw: typeof bodyParser.raw;
|
||||
|
||||
/**
|
||||
* This is a built-in middleware function in Express. It parses incoming requests with text payloads and is based on body-parser.
|
||||
* @since 4.17.0
|
||||
*/
|
||||
var text: typeof bodyParser.text;
|
||||
|
||||
/**
|
||||
* These are the exposed prototypes.
|
||||
*/
|
||||
var application: Application;
|
||||
var request: Request;
|
||||
var response: Response;
|
||||
|
||||
/**
|
||||
* This is a built-in middleware function in Express. It serves static files and is based on serve-static.
|
||||
*/
|
||||
var static: typeof serveStatic;
|
||||
|
||||
/**
|
||||
* This is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is based on body-parser.
|
||||
* @since 4.16.0
|
||||
*/
|
||||
var urlencoded: typeof bodyParser.urlencoded;
|
||||
|
||||
/**
|
||||
* This is a built-in middleware function in Express. It parses incoming request query parameters.
|
||||
*/
|
||||
export function query(options: qs.IParseOptions | typeof qs.parse): Handler;
|
||||
|
||||
export function Router(options?: RouterOptions): core.Router;
|
||||
|
||||
interface RouterOptions {
|
||||
/**
|
||||
* Enable case sensitivity.
|
||||
*/
|
||||
caseSensitive?: boolean;
|
||||
|
||||
/**
|
||||
* Preserve the req.params values from the parent router.
|
||||
* If the parent and the child have conflicting param names, the child’s value take precedence.
|
||||
*
|
||||
* @default false
|
||||
* @since 4.5.0
|
||||
*/
|
||||
mergeParams?: boolean;
|
||||
|
||||
/**
|
||||
* Enable strict routing.
|
||||
*/
|
||||
strict?: boolean;
|
||||
}
|
||||
|
||||
interface Application extends core.Application { }
|
||||
interface CookieOptions extends core.CookieOptions { }
|
||||
interface Errback extends core.Errback { }
|
||||
interface ErrorRequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = core.Query>
|
||||
extends core.ErrorRequestHandler<P, ResBody, ReqBody, ReqQuery> { }
|
||||
interface Express extends core.Express { }
|
||||
interface Handler extends core.Handler { }
|
||||
interface IRoute extends core.IRoute { }
|
||||
interface IRouter extends core.IRouter { }
|
||||
interface IRouterHandler<T> extends core.IRouterHandler<T> { }
|
||||
interface IRouterMatcher<T> extends core.IRouterMatcher<T> { }
|
||||
interface MediaType extends core.MediaType { }
|
||||
interface NextFunction extends core.NextFunction { }
|
||||
interface Request<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery> { }
|
||||
interface RequestHandler<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = core.Query> extends core.RequestHandler<P, ResBody, ReqBody, ReqQuery> { }
|
||||
interface RequestParamHandler extends core.RequestParamHandler { }
|
||||
export interface Response<ResBody = any> extends core.Response<ResBody> { }
|
||||
interface Router extends core.Router { }
|
||||
interface Send extends core.Send { }
|
||||
}
|
||||
|
||||
export = e;
|
109
src/db.ts
Normal file
109
src/db.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import mongoose from 'mongoose';
|
||||
import cfenv from 'cfenv';
|
||||
|
||||
// mongoose.set('debug', true); // enable mongoose debug
|
||||
|
||||
// database urls, prod db url is retrieved automatically
|
||||
const TESTING_URL = 'mongodb://localhost/dfopdb_test';
|
||||
const DEV_URL = 'mongodb://localhost/dfopdb';
|
||||
|
||||
export default class db {
|
||||
private static state = { // db object and current mode (test, dev, prod)
|
||||
db: null,
|
||||
mode: null,
|
||||
};
|
||||
|
||||
static connect (mode = '', done: Function = () => {}) { // set mode to test for unit/integration tests, otherwise skip parameter. done is also only needed for testing
|
||||
if (this.state.db) return done(); // db is already connected
|
||||
|
||||
// find right connection url
|
||||
let connectionString: string = "";
|
||||
if (mode === 'test') { // testing
|
||||
connectionString = TESTING_URL;
|
||||
this.state.mode = 'test';
|
||||
}
|
||||
else if(process.env.NODE_ENV === 'production') {
|
||||
let services = cfenv.getAppEnv().getServices();
|
||||
for (let service in services) {
|
||||
if(services[service].tags.indexOf("mongodb") >= 0) {
|
||||
connectionString = services[service]["credentials"].uri;
|
||||
}
|
||||
}
|
||||
this.state.mode = 'prod';
|
||||
}
|
||||
else {
|
||||
connectionString = DEV_URL;
|
||||
this.state.mode = 'dev';
|
||||
}
|
||||
|
||||
// connect to db
|
||||
mongoose.connect(connectionString, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, connectTimeoutMS: 10000}, err => {
|
||||
if (err) done(err);
|
||||
});
|
||||
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
|
||||
mongoose.connection.on('disconnected', () => { // reset state on disconnect
|
||||
console.log('Database disconnected');
|
||||
this.state.db = 0;
|
||||
done();
|
||||
});
|
||||
process.on('SIGINT', () => { // close connection when app is terminated
|
||||
mongoose.connection.close(() => {
|
||||
console.log('Mongoose default connection disconnected through app termination');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
mongoose.connection.once('open', () => {
|
||||
mongoose.set('useFindAndModify', false);
|
||||
console.log(process.env.NODE_ENV === 'test' ? '' : `Connected to ${connectionString}`);
|
||||
this.state.db = mongoose.connection;
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
static getState () {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
static drop (done: Function = () => {}) { // drop all collections of connected db (only dev and test for safety reasons ;)
|
||||
if (!this.state.db || this.state.mode === 'prod') return done(); // no db connection or prod db
|
||||
this.state.db.db.listCollections().toArray((err, collections) => { // get list of all collections
|
||||
if (collections.length === 0) { // there are no collections to drop
|
||||
return done();
|
||||
}
|
||||
else {
|
||||
let dropCounter = 0; // count number of dropped collections to know when to return done()
|
||||
collections.forEach(collection => { // drop each collection
|
||||
this.state.db.dropCollection(collection.name, () => {
|
||||
if (++ dropCounter >= collections.length) { // all collections dropped
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static loadJson (json, done: Function = () => {}) { // insert given JSON data into db, uses core mongodb methods
|
||||
if (!this.state.db || !json.hasOwnProperty('collections') || json.collections.length === 0) {
|
||||
return done();
|
||||
} // no db connection or nothing to load
|
||||
|
||||
let loadCounter = 0; // count number of loaded collections to know when to return done()
|
||||
Object.keys(json.collections).forEach(collectionName => { // create each collection
|
||||
for(let i in json.collections[collectionName]) { // convert $oid fields to actual ObjectIds
|
||||
Object.keys(json.collections[collectionName][i]).forEach(key => {
|
||||
if (json.collections[collectionName][i][key] !== null && json.collections[collectionName][i][key].hasOwnProperty('$oid')) {
|
||||
json.collections[collectionName][i][key] = mongoose.Types.ObjectId(json.collections[collectionName][i][key].$oid);
|
||||
}
|
||||
})
|
||||
}
|
||||
this.state.db.createCollection(collectionName, (err, collection) => {
|
||||
collection.insertMany(json.collections[collectionName], () => { // insert JSON data
|
||||
if (++ loadCounter >= Object.keys(json.collections).length) { // all collections loaded
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
11
src/globals.ts
Normal file
11
src/globals.ts
Normal file
@ -0,0 +1,11 @@
|
||||
const globals = {
|
||||
levels: [ // access levels
|
||||
'read',
|
||||
'write',
|
||||
'maintain',
|
||||
'dev',
|
||||
'admin'
|
||||
]
|
||||
};
|
||||
|
||||
export default globals;
|
101
src/helpers/authorize.ts
Normal file
101
src/helpers/authorize.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import basicAuth from 'basic-auth';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import UserModel from '../models/user';
|
||||
|
||||
|
||||
// appends req.auth(res, ['levels'], method = 'all')
|
||||
// which returns sends error message and returns false if unauthorized, otherwise true
|
||||
// req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'}
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
let givenMethod = ''; // authorization method given by client, basic taken preferred
|
||||
let user = {name: '', level: '', id: ''}; // user object
|
||||
|
||||
// test authentications
|
||||
const userBasic = await basic(req, next);
|
||||
|
||||
if (userBasic) { // basic available
|
||||
givenMethod = 'basic';
|
||||
user = userBasic;
|
||||
}
|
||||
else { // if basic not available, test key
|
||||
const userKey = await key(req, next);
|
||||
if (userKey) {
|
||||
givenMethod = 'key';
|
||||
user = userKey;
|
||||
}
|
||||
}
|
||||
|
||||
req.auth = (res, levels, method = 'all') => {
|
||||
if (givenMethod === method || (method === 'all' && givenMethod !== '')) { // method is available
|
||||
if (levels.indexOf(user.level) > -1) { // level is available
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
res.status(403).json({status: 'Forbidden'});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.status(401).json({status: 'Unauthorized'});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
req.authDetails = {
|
||||
method: givenMethod,
|
||||
username: user.name,
|
||||
level: user.level,
|
||||
id: user.id
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
function basic (req, next): any { // checks basic auth and returns changed user object
|
||||
return new Promise(resolve => {
|
||||
const auth = basicAuth(req);
|
||||
if (auth !== undefined) { // basic auth available
|
||||
UserModel.find({name: auth.name}).lean().exec( (err, data: any) => { // find user
|
||||
if (err) return next(err);
|
||||
if (data.length === 1) { // one user found
|
||||
bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // check password
|
||||
if (err) return next(err);
|
||||
if (res === true) {
|
||||
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString()});
|
||||
}
|
||||
else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function key (req, next): any { // checks API key and returns changed user object
|
||||
return new Promise(resolve => {
|
||||
if (req.query.key !== undefined) {
|
||||
UserModel.find({key: req.query.key}).lean().exec( (err, data: any) => { // find user
|
||||
if (err) return next(err);
|
||||
if (data.length === 1) { // one user found
|
||||
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString()});
|
||||
}
|
||||
else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
64
src/helpers/mail.ts
Normal file
64
src/helpers/mail.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// sends an email
|
||||
|
||||
export default (mailAddress, subject, content, f) => { // callback, executed empty or with error
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const mailService = JSON.parse(process.env.VCAP_SERVICES).Mail[0];
|
||||
axios({
|
||||
method: 'post',
|
||||
url: mailService.credentials.uri + '/email',
|
||||
auth: {username: mailService.credentials.username, password: mailService.credentials.password},
|
||||
data: {
|
||||
recipients: [{to: mailAddress}],
|
||||
subject: {content: subject},
|
||||
body: {
|
||||
content: content,
|
||||
contentType: "text/html"
|
||||
},
|
||||
from: {
|
||||
eMail: "dfop@bosch-iot.com",
|
||||
password: "PlasticsOfFingerprintDigital"
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
f();
|
||||
})
|
||||
.catch((err) => {
|
||||
f(err);
|
||||
});
|
||||
}
|
||||
else if (process.env.NODE_ENV === 'test') {
|
||||
console.log('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
|
||||
f();
|
||||
}
|
||||
else { // dev
|
||||
axios({
|
||||
method: 'get',
|
||||
url: 'https://digital-fingerprint-of-plastics-mail-test.apps.de1.bosch-iot-cloud.com/api',
|
||||
data: {
|
||||
method: 'post',
|
||||
url: '/email',
|
||||
data: {
|
||||
recipients: [{to: mailAddress}],
|
||||
subject: {content: subject},
|
||||
body: {
|
||||
content: content,
|
||||
contentType: "text/html"
|
||||
},
|
||||
from: {
|
||||
eMail: "dfop-test@bosch-iot.com",
|
||||
password: "PlasticsOfFingerprintDigital"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
f();
|
||||
})
|
||||
.catch((err) => {
|
||||
f(err);
|
||||
});
|
||||
}
|
||||
}
|
90
src/helpers/test.ts
Normal file
90
src/helpers/test.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import supertest from 'supertest';
|
||||
import should from 'should/as-function';
|
||||
import db from "../db";
|
||||
|
||||
|
||||
export default class TestHelper {
|
||||
public static auth = {
|
||||
admin: {pass: 'Abc123!#', key: '000000000000000000001003'},
|
||||
janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002'},
|
||||
user: {pass: 'Xyz890*)', key: '000000000000000000001001'}
|
||||
}
|
||||
public static res = {
|
||||
400: {status: 'Bad request'},
|
||||
401: {status: 'Unauthorized'},
|
||||
403: {status: 'Forbidden'},
|
||||
404: {status: 'Not found'},
|
||||
500: {status: 'Internal server error'}
|
||||
}
|
||||
|
||||
static before (done) {
|
||||
process.env.port = '2999';
|
||||
process.env.NODE_ENV = 'test';
|
||||
db.connect('test', done);
|
||||
}
|
||||
|
||||
static beforeEach (server, done) {
|
||||
delete require.cache[require.resolve('../index')]; // prevent loading from cache
|
||||
server = require('../index');
|
||||
db.drop(err => { // reset database
|
||||
if (err) return done(err);
|
||||
db.loadJson(require('../test/db.json'), done);
|
||||
});
|
||||
return server
|
||||
}
|
||||
|
||||
static afterEach (server, done) {
|
||||
server.close(done);
|
||||
}
|
||||
|
||||
static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res}
|
||||
let st = supertest(server);
|
||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) {
|
||||
options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
|
||||
}
|
||||
switch (options.method) {
|
||||
case 'get':
|
||||
st = st.get(options.url)
|
||||
break;
|
||||
case 'post':
|
||||
st = st.post(options.url)
|
||||
break;
|
||||
case 'put':
|
||||
st = st.put(options.url)
|
||||
break;
|
||||
case 'delete':
|
||||
st = st.delete(options.url)
|
||||
break;
|
||||
}
|
||||
if (options.hasOwnProperty('req')) {
|
||||
st = st.send(options.req);
|
||||
}
|
||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) {
|
||||
if (this.auth.hasOwnProperty(options.auth.basic)) {
|
||||
st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
|
||||
}
|
||||
else {
|
||||
st = st.auth(options.auth.basic.name, options.auth.basic.pass)
|
||||
}
|
||||
}
|
||||
st = st.expect('Content-type', /json/)
|
||||
.expect(options.httpStatus);
|
||||
if (options.hasOwnProperty('res')) {
|
||||
return st.end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql(options.res);
|
||||
done();
|
||||
});
|
||||
}
|
||||
else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) {
|
||||
return st.end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql(this.res[options.httpStatus]);
|
||||
done();
|
||||
});
|
||||
}
|
||||
else {
|
||||
return st;
|
||||
}
|
||||
}
|
||||
}
|
90
src/index.ts
90
src/index.ts
@ -1,37 +1,18 @@
|
||||
import cfenv from 'cfenv';
|
||||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
import bodyParser from 'body-parser';
|
||||
import swagger from 'swagger-ui-express';
|
||||
import jsonRefParser, {JSONSchema} from '@apidevtools/json-schema-ref-parser';
|
||||
import contentFilter from 'content-filter';
|
||||
import mongoSanitize from 'mongo-sanitize';
|
||||
import db from './db';
|
||||
|
||||
|
||||
// tell if server is running in debug or production environment
|
||||
console.log(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : '===== DEVELOPMENT =====');
|
||||
|
||||
|
||||
// get mongodb address from server, otherwise set to localhost
|
||||
let connectionString: string = "";
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
let services = cfenv.getAppEnv().getServices();
|
||||
for (let service in services) {
|
||||
if(services[service].tags.indexOf("mongodb") >= 0) {
|
||||
connectionString = services[service]["credentials"].uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
connectionString = 'mongodb://localhost/dfopdb';
|
||||
}
|
||||
mongoose.connect(connectionString, {useNewUrlParser: true, useUnifiedTopology: true});
|
||||
|
||||
// connect to mongodb
|
||||
let db = mongoose.connection;
|
||||
db.on('error', console.error.bind(console, 'connection error:'));
|
||||
db.once('open', () => {
|
||||
console.log(`Connected to ${connectionString}`);
|
||||
});
|
||||
console.log(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
|
||||
|
||||
|
||||
// mongodb connection
|
||||
db.connect();
|
||||
|
||||
// create Express app
|
||||
const app = express();
|
||||
@ -40,20 +21,61 @@ app.disable('x-powered-by');
|
||||
// get port from environment, defaults to 3000
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
//middleware
|
||||
app.use(express.json({ limit: '5mb'}));
|
||||
app.use(express.urlencoded({ extended: false, limit: '5mb' }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(contentFilter()); // filter URL query attacks
|
||||
app.use((req, res, next) => { // filter body query attacks
|
||||
req.body = mongoSanitize(req.body);
|
||||
next();
|
||||
});
|
||||
app.use((err, req, res, ignore) => { // bodyParser error handling
|
||||
res.status(400).send({status: 'Invalid JSON body'});
|
||||
});
|
||||
app.use((req, res, next) => { // no database connection error
|
||||
if (db.getState().db) {
|
||||
next();
|
||||
}
|
||||
else {
|
||||
res.status(500).send({status: 'Internal server error'});
|
||||
}
|
||||
});
|
||||
app.use(require('./helpers/authorize')); // handle authentication
|
||||
|
||||
// require routes
|
||||
app.use('/', require('./routes/root'));
|
||||
app.use('/', require('./routes/sample'));
|
||||
app.use('/', require('./routes/material'));
|
||||
app.use('/', require('./routes/template'));
|
||||
app.use('/', require('./routes/user'));
|
||||
|
||||
// static files
|
||||
app.use('/static', express.static('static'));
|
||||
|
||||
// Swagger UI
|
||||
let oasDoc: JSONSchema = {};
|
||||
jsonRefParser.bundle('oas/oas.yaml', (err, doc) => {
|
||||
let apiDoc: JSONSchema = {};
|
||||
jsonRefParser.bundle('api/api.yaml', (err, doc) => {
|
||||
if(err) throw err;
|
||||
oasDoc = doc;
|
||||
oasDoc.paths = oasDoc.paths.allOf.reduce((s, e) => Object.assign(s, e));
|
||||
swagger.setup(oasDoc, {defaultModelsExpandDepth: -1, customCss: '.swagger-ui .topbar { display: none }'});
|
||||
apiDoc = doc;
|
||||
apiDoc.paths = apiDoc.paths.allOf.reduce((s, e) => Object.assign(s, e));
|
||||
swagger.setup(apiDoc, {defaultModelsExpandDepth: -1, customCss: '.swagger-ui .topbar { display: none }'});
|
||||
});
|
||||
app.use('/api', swagger.serve, swagger.setup(oasDoc, {defaultModelsExpandDepth: -1, customCss: '.swagger-ui .topbar { display: none }'}));
|
||||
app.use('/api', swagger.serve, swagger.setup(apiDoc, {customCssUrl: '/static/styles/swagger.css'}));
|
||||
|
||||
app.use((req, res) => { // 404 error handling
|
||||
res.status(404).json({status: 'Not found'});
|
||||
});
|
||||
|
||||
app.use((err, req, res, ignore) => { // internal server error handling
|
||||
console.error(err);
|
||||
res.status(500).json({status: 'Internal server error'});
|
||||
});
|
||||
|
||||
|
||||
// hook up server to port
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on http;//localhost:${port}`);
|
||||
const server = app.listen(port, () => {
|
||||
console.log(process.env.NODE_ENV === 'test' ? '' : `Listening on http://localhost:${port}`);
|
||||
});
|
||||
|
||||
module.exports = server;
|
16
src/models/material.ts
Normal file
16
src/models/material.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const MaterialSchema = new mongoose.Schema({
|
||||
name: {type: String, index: {unique: true}},
|
||||
supplier: String,
|
||||
group: String,
|
||||
mineral: String,
|
||||
glass_fiber: String,
|
||||
carbon_fiber: String,
|
||||
numbers: [{
|
||||
color: String,
|
||||
number: Number
|
||||
}]
|
||||
});
|
||||
|
||||
export default mongoose.model('material', MaterialSchema);
|
11
src/models/measurement_template.ts
Normal file
11
src/models/measurement_template.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const MeasurementTemplateSchema = new mongoose.Schema({
|
||||
name: {type: String, index: {unique: true}},
|
||||
parameters: [{
|
||||
name: String,
|
||||
range: mongoose.Schema.Types.Mixed
|
||||
}]
|
||||
}, {minimize: false}); // to allow empty objects
|
||||
|
||||
export default mongoose.model('measurement_template', MeasurementTemplateSchema);
|
12
src/models/note.ts
Normal file
12
src/models/note.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const NoteSchema = new mongoose.Schema({
|
||||
comment: String,
|
||||
sample_references: [{
|
||||
id: mongoose.Schema.Types.ObjectId,
|
||||
relation: String
|
||||
}],
|
||||
custom_fields: mongoose.Schema.Types.Mixed
|
||||
});
|
||||
|
||||
export default mongoose.model('note', NoteSchema);
|
8
src/models/note_field.ts
Normal file
8
src/models/note_field.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const NoteFieldSchema = new mongoose.Schema({
|
||||
name: {type: String, index: {unique: true}},
|
||||
qty: Number
|
||||
});
|
||||
|
||||
export default mongoose.model('note_field', NoteFieldSchema);
|
18
src/models/sample.ts
Normal file
18
src/models/sample.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
import MaterialModel from './material';
|
||||
import NoteModel from './note';
|
||||
import UserModel from './user';
|
||||
|
||||
const SampleSchema = new mongoose.Schema({
|
||||
number: {type: String, index: {unique: true}},
|
||||
type: String,
|
||||
color: String,
|
||||
batch: String,
|
||||
validated: Boolean,
|
||||
material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
|
||||
note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
|
||||
user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel}
|
||||
});
|
||||
|
||||
export default mongoose.model('sample', SampleSchema);
|
11
src/models/treatment_template.ts
Normal file
11
src/models/treatment_template.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const TreatmentTemplateSchema = new mongoose.Schema({
|
||||
name: {type: String, index: {unique: true}},
|
||||
parameters: [{
|
||||
name: String,
|
||||
range: mongoose.Schema.Types.Mixed
|
||||
}]
|
||||
}, {minimize: false}); // to allow empty objects
|
||||
|
||||
export default mongoose.model('treatment_template', TreatmentTemplateSchema);
|
13
src/models/user.ts
Normal file
13
src/models/user.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const UserSchema = new mongoose.Schema({
|
||||
name: {type: String, index: {unique: true}},
|
||||
email: String,
|
||||
pass: String,
|
||||
key: String,
|
||||
level: String,
|
||||
location: String,
|
||||
device_name: String
|
||||
});
|
||||
|
||||
export default mongoose.model('user', UserSchema);
|
397
src/routes/material.spec.ts
Normal file
397
src/routes/material.spec.ts
Normal file
@ -0,0 +1,397 @@
|
||||
import should from 'should/as-function';
|
||||
import MaterialModel from '../models/material';
|
||||
import TestHelper from "../helpers/test";
|
||||
|
||||
|
||||
describe('/material', () => {
|
||||
let server;
|
||||
before(done => TestHelper.before(done));
|
||||
beforeEach(done => server = TestHelper.beforeEach(server, done));
|
||||
afterEach(done => TestHelper.afterEach(server, done));
|
||||
|
||||
describe('GET /materials', () => {
|
||||
it('returns all materials', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials',
|
||||
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.materials.length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
should(material).have.property('_id').be.type('string');
|
||||
should(material).have.property('name').be.type('string');
|
||||
should(material).have.property('supplier').be.type('string');
|
||||
should(material).have.property('group').be.type('string');
|
||||
should(material).have.property('mineral').be.type('number');
|
||||
should(material).have.property('glass_fiber').be.type('number');
|
||||
should(material).have.property('carbon_fiber').be.type('number');
|
||||
should(material.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color').be.type('string');
|
||||
should(number).have.property('number').be.type('number');
|
||||
});
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('works with an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials',
|
||||
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.materials.length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
should(material).have.property('_id').be.type('string');
|
||||
should(material).have.property('name').be.type('string');
|
||||
should(material).have.property('supplier').be.type('string');
|
||||
should(material).have.property('group').be.type('string');
|
||||
should(material).have.property('mineral').be.type('number');
|
||||
should(material).have.property('glass_fiber').be.type('number');
|
||||
should(material).have.property('carbon_fiber').be.type('number');
|
||||
should(material.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color').be.type('string');
|
||||
should(number).have.property('number').be.type('number');
|
||||
});
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /material/{id}', () => {
|
||||
it('returns the right material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}, {color: 'natural', number: 5514263422}]}
|
||||
});
|
||||
});
|
||||
it('returns the right material for an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/material/100000000000000000000003',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 200,
|
||||
res: {_id: '100000000000000000000003', name: 'PA GF 50 black (2706)', supplier: 'Akro-Plastic', group: 'PA66+PA6I/6T', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: []}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/material/10000000000000000000000x',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects an unknown id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/material/100000000000000000000111',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/material/100000000000000000000001',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /material/{id}', () => {
|
||||
it('returns the right material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {},
|
||||
res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}, {color: 'natural', number: 5514263422}]}
|
||||
});
|
||||
});
|
||||
it('keeps unchanged properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]},
|
||||
res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]}
|
||||
});
|
||||
});
|
||||
it('changes the given properties', 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}]}
|
||||
,
|
||||
}).end((err, res) => {
|
||||
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}]});
|
||||
MaterialModel.findById('100000000000000000000001').lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
data._id = data._id.toString({_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}]});
|
||||
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}], __v: 0}
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects already existing material names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Ultramid T KR 4355 G7'},
|
||||
res: {status: 'Material name already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects wrong material properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {mineral: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/10000000000000000000000x',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 404,
|
||||
req: {},
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000002',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a read user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000002',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('returns 404 for an unknown material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000111',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 404,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000001',
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /material/{id}', () => {
|
||||
it('deletes the material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({status: 'OK'});
|
||||
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).be.null();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects deleting a material referenced by samples');
|
||||
it('rejects an invalid id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/10000000000000000000000x',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/100000000000000000000002',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects requests from a read user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/100000000000000000000002',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403
|
||||
});
|
||||
});
|
||||
it('returns 404 for an unknown id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/100000000000000000000111',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/100000000000000000000001',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /material/new', () => {
|
||||
it('returns the right material', 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: [{color: 'black', number: 5515798402}]}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('name', 'Crastin CE 2510');
|
||||
should(res.body).have.property('supplier', 'Du Pont');
|
||||
should(res.body).have.property('group', 'PBT');
|
||||
should(res.body).have.property('mineral', 0);
|
||||
should(res.body).have.property('glass_fiber', 30);
|
||||
should(res.body).have.property('carbon_fiber', 0);
|
||||
should(res.body.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color', 'black');
|
||||
should(number).have.property('number', 5515798402);
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('stores the material', 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: []}
|
||||
}).end(err => {
|
||||
if (err) return done (err);
|
||||
MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
|
||||
if (err) return done (err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', '__v');
|
||||
should(data[0]).have.property('_id');
|
||||
should(data[0]).have.property('name', 'Crastin CE 2510');
|
||||
should(data[0]).have.property('supplier', 'Du Pont');
|
||||
should(data[0]).have.property('group', 'PBT');
|
||||
should(data[0]).have.property('mineral', '0');
|
||||
should(data[0]).have.property('glass_fiber', '30');
|
||||
should(data[0]).have.property('carbon_fiber', '0');
|
||||
should(data[0].numbers).have.lengthOf(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects already existing material names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]},
|
||||
res: {status: 'Material name already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects wrong material properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects incomplete material properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a read user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
httpStatus: 401,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
110
src/routes/material.ts
Normal file
110
src/routes/material.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import express from 'express';
|
||||
|
||||
import MaterialValidate from './validate/material';
|
||||
import MaterialModel from '../models/material'
|
||||
import IdValidate from './validate/id';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/materials', (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
|
||||
|
||||
MaterialModel.find({}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(data.map(e => MaterialValidate.output(e)).filter(e => e !== null)); // validate all and filter null values from validation errors
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
|
||||
|
||||
MaterialModel.findById(req.params.id).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(MaterialValidate.output(data));
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
const {error, value: material} = MaterialValidate.input(req.body, 'change');
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (material.hasOwnProperty('name')) {
|
||||
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data.length > 0 && data[0]._id != req.params.id) {
|
||||
res.status(400).json({status: 'Material name already taken'});
|
||||
return;
|
||||
}
|
||||
else {
|
||||
f();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
f();
|
||||
}
|
||||
|
||||
function f() { // to resolve async
|
||||
MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(MaterialValidate.output(data));
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
MaterialModel.findByIdAndDelete(req.params.id).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json({status: 'OK'})
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/material/new', (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
// validate input
|
||||
const {error, value: material} = MaterialValidate.input(req.body, 'new');
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data.length > 0) {
|
||||
res.status(400).json({status: 'Material name already taken'});
|
||||
return;
|
||||
}
|
||||
|
||||
new MaterialModel(material).save((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(MaterialValidate.output(data.toObject()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
@ -1,19 +1,69 @@
|
||||
import supertest from 'supertest';
|
||||
import should from 'should/as-function';
|
||||
import TestHelper from "../helpers/test";
|
||||
|
||||
|
||||
let server = supertest.agent('http://localhost:3000');
|
||||
describe('/', () => {
|
||||
let server;
|
||||
before(done => TestHelper.before(done));
|
||||
beforeEach(done => server = TestHelper.beforeEach(server, done));
|
||||
afterEach(done => TestHelper.afterEach(server, done));
|
||||
|
||||
describe('Testing /', () => {
|
||||
it('returns the message object', done => {
|
||||
server
|
||||
.get('/')
|
||||
.expect('Content-type', /json/)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
should(res.statusCode).equal(200);
|
||||
should(res.body).be.eql({message: 'API server up and running!'});
|
||||
done();
|
||||
describe('GET /', () => {
|
||||
it('returns the root message', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/',
|
||||
httpStatus: 200,
|
||||
res: {status: 'API server up and running!'}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unknown routes', () => {
|
||||
it('return a 404 message', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/unknownroute',
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('An unauthorized request', () => {
|
||||
it('returns a 401 message', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/authorized',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('does not work with correct username', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/authorized',
|
||||
auth: {name: 'admin', pass: 'Abc123!!'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('An authorized request', () => {
|
||||
it('works with an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/authorized',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 200,
|
||||
res: {status: 'Authorization successful', method: 'key'}
|
||||
});
|
||||
});
|
||||
it('works with basic auth', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/authorized',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
res: {status: 'Authorization successful', method: 'basic'}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,9 +1,15 @@
|
||||
import express from 'express';
|
||||
import globals from '../globals';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.json({message: 'API server up and running!'});
|
||||
res.json({status: 'API server up and running!'});
|
||||
});
|
||||
|
||||
router.get('/authorized', (req, res) => {
|
||||
if (!req.auth(res, globals.levels)) return;
|
||||
res.json({status: 'Authorization successful', method: req.authDetails.method});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
336
src/routes/sample.spec.ts
Normal file
336
src/routes/sample.spec.ts
Normal file
@ -0,0 +1,336 @@
|
||||
import should from 'should/as-function';
|
||||
import SampleModel from '../models/sample';
|
||||
import NoteModel from '../models/note';
|
||||
import NoteFieldModel from '../models/note_field';
|
||||
import TestHelper from "../helpers/test";
|
||||
|
||||
|
||||
describe('/sample', () => {
|
||||
let server;
|
||||
before(done => TestHelper.before(done));
|
||||
beforeEach(done => server = TestHelper.beforeEach(server, done));
|
||||
afterEach(done => TestHelper.afterEach(server, done));
|
||||
|
||||
describe('GET /samples', () => {
|
||||
it('returns all samples', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples',
|
||||
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.samples.length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(material).have.property('_id').be.type('string');
|
||||
should(material).have.property('number').be.type('string');
|
||||
should(material).have.property('type').be.type('string');
|
||||
should(material).have.property('color').be.type('string');
|
||||
should(material).have.property('batch').be.type('string');
|
||||
should(material).have.property('material_id').be.type('string');
|
||||
should(material).have.property('note_id');
|
||||
should(material).have.property('user_id').be.type('string');
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('works with an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples',
|
||||
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.samples.length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(material).have.property('_id').be.type('string');
|
||||
should(material).have.property('number').be.type('string');
|
||||
should(material).have.property('type').be.type('string');
|
||||
should(material).have.property('color').be.type('string');
|
||||
should(material).have.property('batch').be.type('string');
|
||||
should(material).have.property('material_id').be.type('string');
|
||||
should(material).have.property('note_id');
|
||||
should(material).have.property('user_id').be.type('string');
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /sample/new', () => {
|
||||
it('returns the right sample', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{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', 'material_id', 'note_id', 'user_id');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('number', 'Rng172');
|
||||
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('material_id', '100000000000000000000001');
|
||||
should(res.body).have.property('note_id').be.type('string');
|
||||
should(res.body).have.property('user_id', '000000000000000000000002');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('stores the sample', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
}).end(err => {
|
||||
if (err) return done (err);
|
||||
SampleModel.find({number: 'Rng172'}).lean().exec((err, data: any) => {
|
||||
if (err) return done (err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', '__v');
|
||||
should(data[0]).have.property('_id');
|
||||
should(data[0]).have.property('number', 'Rng172');
|
||||
should(data[0]).have.property('color', 'black');
|
||||
should(data[0]).have.property('type', 'granulate');
|
||||
should(data[0]).have.property('batch', '1560237365');
|
||||
should(data[0].material_id.toString()).be.eql('100000000000000000000001');
|
||||
should(data[0].user_id.toString()).be.eql('000000000000000000000002');
|
||||
should(data[0]).have.property('note_id');
|
||||
NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => {
|
||||
if (err) return done (err);
|
||||
should(data).have.property('_id');
|
||||
should(data).have.property('comment', 'Testcomment');
|
||||
should(data).have.property('sample_references');
|
||||
should(data.sample_references).have.lengthOf(1);
|
||||
should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
|
||||
should(data.sample_references[0]).have.property('relation', 'part to this sample');
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
it('stores the custom fields', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [], custom_fields: {field1: 'a', field2: 'b', 'not allowed for new applications': true}}}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
NoteModel.findById(res.body.note_id).lean().exec((err, data: any) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.property('_id');
|
||||
should(data).have.property('comment', 'Testcomment');
|
||||
should(data).have.property('sample_references').have.lengthOf(0);
|
||||
should(data).have.property('custom_fields');
|
||||
should(data.custom_fields).have.property('field1', 'a');
|
||||
should(data.custom_fields).have.property('field2', 'b');
|
||||
should(data.custom_fields).have.property('not allowed for new applications', true);
|
||||
NoteFieldModel.find({name: 'field1'}).lean().exec((err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.property('qty', 1);
|
||||
NoteFieldModel.find({name: 'field2'}).lean().exec((err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.property('qty', 1);
|
||||
NoteFieldModel.find({name: 'not allowed for new applications'}).lean().exec((err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.property('qty', 3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects a color not defined for the material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', color: 'green', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Color not available for material'}
|
||||
});
|
||||
});
|
||||
it('rejects an unknown material id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '000000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Material not available'}
|
||||
});
|
||||
});
|
||||
it('rejects a sample number in use', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: '1', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Sample number already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid sample reference', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Sample reference not available'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing color', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing sample number', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing type', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing batch', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing material id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid material id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a read user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
httpStatus: 401,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /sample/notes/fields', () => {
|
||||
it('returns all fields', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/sample/notes/fields',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
should(res.body).have.lengthOf(json.collections.note_fields.length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('name', 'qty');
|
||||
should(material).have.property('qty').be.type('number');
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('works with an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/sample/notes/fields',
|
||||
auth: {key: 'user'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
should(res.body).have.lengthOf(json.collections.note_fields.length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('name', 'qty');
|
||||
should(material).have.property('qty').be.type('number');
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/sample/notes/fields',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
109
src/routes/sample.ts
Normal file
109
src/routes/sample.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import express from 'express';
|
||||
|
||||
import SampleValidate from './validate/sample';
|
||||
import NoteFieldValidate from './validate/note_field';
|
||||
import SampleModel from '../models/sample'
|
||||
import MaterialModel from '../models/material';
|
||||
import NoteModel from '../models/note';
|
||||
import NoteFieldModel from '../models/note_field';
|
||||
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/samples', (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
|
||||
|
||||
SampleModel.find({}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(data.map(e => SampleValidate.output(e)).filter(e => e !== null)); // validate all and filter null values from validation errors
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
router.post('/sample/new', (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
const {error, value: sample} = SampleValidate.input(req.body, 'new');
|
||||
if (error) {
|
||||
return res.status(400).json({status: 'Invalid body format'});
|
||||
}
|
||||
|
||||
MaterialModel.findById(sample.material_id).lean().exec((err, data: any) => { // validate material_id
|
||||
if (err) return next(err);
|
||||
if (!data) { // could not find material_id
|
||||
return res.status(400).json({status: 'Material not available'});
|
||||
}
|
||||
if (!data.numbers.find(e => e.color === sample.color)) { // color for material not specified
|
||||
return res.status(400).json({status: 'Color not available for material'});
|
||||
}
|
||||
SampleModel.findOne({number: sample.number}).lean().exec((err, data) => { // validate sample number
|
||||
if (err) return next(err);
|
||||
if (data) { // found entry with sample number
|
||||
return res.status(400).json({status: 'Sample number already taken'});
|
||||
}
|
||||
|
||||
if (sample.notes.sample_references.length > 0) { // validate sample_references
|
||||
let referencesCount = sample.notes.sample_references.length;
|
||||
sample.notes.sample_references.forEach(reference => {
|
||||
SampleModel.findById(reference.id).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (!data) {
|
||||
return res.status(400).json({status: 'Sample reference not available'});
|
||||
}
|
||||
referencesCount --;
|
||||
if (referencesCount <= 0) {
|
||||
f();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
f();
|
||||
}
|
||||
|
||||
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {
|
||||
customFieldsAdd(Object.keys(sample.notes.custom_fields));
|
||||
}
|
||||
|
||||
function f() { // to resolve async
|
||||
new NoteModel(sample.notes).save((err, data) => {
|
||||
if (err) return next(err);
|
||||
delete sample.notes;
|
||||
sample.note_id = data._id;
|
||||
sample.user_id = req.authDetails.id;
|
||||
new SampleModel(sample).save((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(SampleValidate.output(data.toObject()));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
router.get('/sample/notes/fields', (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
|
||||
|
||||
NoteFieldModel.find({}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(data.map(e => NoteFieldValidate.output(e)).filter(e => e !== null)); // validate all and filter null values from validation errors
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
function customFieldsAdd (fields) {
|
||||
fields.forEach(field => {
|
||||
NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: 1}}).lean().exec((err, data) => { // check if field exists
|
||||
if (err) return console.error(err);
|
||||
if (!data) { // new field
|
||||
new NoteFieldModel({name: field, qty: 1}).save(err => {
|
||||
if (err) return console.error(err);
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
578
src/routes/template.spec.ts
Normal file
578
src/routes/template.spec.ts
Normal file
@ -0,0 +1,578 @@
|
||||
import should from 'should/as-function';
|
||||
import TemplateTreatmentModel from '../models/treatment_template';
|
||||
import TemplateMeasurementModel from '../models/measurement_template';
|
||||
import TestHelper from "../helpers/test";
|
||||
|
||||
|
||||
describe('/template', () => {
|
||||
let server;
|
||||
before(done => TestHelper.before(done));
|
||||
beforeEach(done => server = TestHelper.beforeEach(server, done));
|
||||
afterEach(done => TestHelper.afterEach(server, done));
|
||||
|
||||
describe('/template/treatment', () => {
|
||||
describe('GET /template/treatments', () => {
|
||||
it('returns all treatment templates', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/treatments',
|
||||
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.treatment_templates.length);
|
||||
should(res.body).matchEach(treatment => {
|
||||
should(treatment).have.only.keys('_id', 'name', 'parameters');
|
||||
should(treatment).have.property('_id').be.type('string');
|
||||
should(treatment).have.property('name').be.type('string');
|
||||
should(treatment.parameters).matchEach(number => {
|
||||
should(number).have.only.keys('name', 'range');
|
||||
should(number).have.property('name').be.type('string');
|
||||
should(number).have.property('range').be.type('object');
|
||||
});
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/treatments',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/treatments',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /template/treatment/{name}', () => {
|
||||
it('returns the right treatment template', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects an unknown name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/treatment/xxx',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /template/treatment/{name}', () => {
|
||||
it('returns the right treatment template', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {},
|
||||
res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
|
||||
});
|
||||
});
|
||||
it('keeps unchanged properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]},
|
||||
res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
|
||||
});
|
||||
});
|
||||
it('changes the given properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]});
|
||||
TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'parameters');
|
||||
should(data[0]).have.property('name', 'heat aging');
|
||||
should(data[0]).have.property('parameters').have.lengthOf(1);
|
||||
should(data[0].parameters[0]).have.property('name', 'time');
|
||||
should(data[0].parameters[0]).have.property('range');
|
||||
should(data[0].parameters[0].range).have.property('min', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('supports values ranges', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: [{name: 'time', range: {values: [1, 2, 5]}}]},
|
||||
res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'time', range: {values: [1, 2, 5]}}]}
|
||||
});
|
||||
});
|
||||
it('supports min max ranges', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: [{name: 'time', range: {min: 1, max: 11}}]},
|
||||
res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'time', range: {min: 1, max: 11}}]}
|
||||
});
|
||||
});
|
||||
it('supports empty ranges', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: [{name: 'time', range: {}}]},
|
||||
res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'time', range: {}}]}
|
||||
});
|
||||
});
|
||||
it('adds a new template for an unknown name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20aging',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
|
||||
}).end(err => {
|
||||
if (err) return done(err);
|
||||
TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'parameters', '__v');
|
||||
should(data[0]).have.property('name', 'heat aging');
|
||||
should(data[0].parameters[0]).have.property('name', 'time');
|
||||
should(data[0].parameters[0]).have.property('range');
|
||||
should(data[0].parameters[0].range).have.property('min', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects an incomplete template for a new name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20aging',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {parameters: [{name: 'time'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects already existing names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'heat treatment 2', parameters: [{name: 'time', range: {min: 1}}]},
|
||||
res: {status: 'Template name already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects wrong properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20aging',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {parameters: [{name: 'time'}], xx: 33},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a write user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /template/treatment/{name}', () => {
|
||||
it('deletes the template', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({status: 'OK'});
|
||||
TemplateTreatmentModel.find({name: 'heat treatment'}).lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects deleting a template still in use');
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects requests from a write user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403
|
||||
})
|
||||
});
|
||||
it('returns 404 for an unknown name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/treatment/xxx',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 404
|
||||
})
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/treatment/heat%20treatment',
|
||||
httpStatus: 401
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/template/measurement', () => {
|
||||
describe('GET /template/measurements', () => {
|
||||
it('returns all measurement templates', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/measurements',
|
||||
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.measurement_templates.length);
|
||||
should(res.body).matchEach(measurement => {
|
||||
should(measurement).have.only.keys('_id', 'name', 'parameters');
|
||||
should(measurement).have.property('_id').be.type('string');
|
||||
should(measurement).have.property('name').be.type('string');
|
||||
should(measurement.parameters).matchEach(number => {
|
||||
should(number).have.only.keys('name', 'range');
|
||||
should(number).have.property('name').be.type('string');
|
||||
should(number).have.property('range').be.type('object');
|
||||
});
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/measurements',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/measurements',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /template/measurement/{name}', () => {
|
||||
it('returns the right measurement template', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {}}]}
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects an unknown name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/measurement/xxx',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/template/measurement/spectrum',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /template/measurement/{name}', () => {
|
||||
it('returns the right measurement template', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {},
|
||||
res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {}}]}
|
||||
});
|
||||
});
|
||||
it('keeps unchanged properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'spectrum', parameters: [{name: 'dpt', range: {}}]},
|
||||
res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {}}]}
|
||||
});
|
||||
});
|
||||
it('changes the given properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]},
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({_id: '300000000000000000000001', name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]});
|
||||
TemplateMeasurementModel.find({name: 'IR spectrum'}).lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'parameters');
|
||||
should(data[0]).have.property('name', 'IR spectrum');
|
||||
should(data[0]).have.property('parameters').have.lengthOf(1);
|
||||
should(data[0].parameters[0]).have.property('name', 'data point table');
|
||||
should(data[0].parameters[0]).have.property('range');
|
||||
should(data[0].parameters[0].range).have.property('min', 0);
|
||||
should(data[0].parameters[0].range).have.property('max', 1000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('supports values ranges', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]},
|
||||
res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]}
|
||||
});
|
||||
});
|
||||
it('supports min max ranges', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]},
|
||||
res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]}
|
||||
});
|
||||
});
|
||||
it('supports empty ranges', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/kf',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: [{name: 'weight %', range: {}}]},
|
||||
res: {_id: '300000000000000000000002', name: 'kf', parameters: [{name: 'weight %', range: {}}]}
|
||||
});
|
||||
});
|
||||
it('adds a new template for an unknown name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/vz',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]}
|
||||
}).end(err => {
|
||||
if (err) return done(err);
|
||||
TemplateMeasurementModel.find({name: 'vz'}).lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'parameters', '__v');
|
||||
should(data[0]).have.property('name', 'vz');
|
||||
should(data[0]).have.property('parameters').have.lengthOf(1);
|
||||
should(data[0].parameters[0]).have.property('name', 'vz');
|
||||
should(data[0].parameters[0]).have.property('range');
|
||||
should(data[0].parameters[0].range).have.property('min', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects an incomplete template for a new name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/vz',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {parameters: [{name: 'vz'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects already existing names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'kf', parameters: [{name: 'dpt', range: {min: 1}}]},
|
||||
res: {status: 'Template name already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects wrong properties', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {parameters: [{name: 'dpt'}], xx: 33},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a write user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/spectrum',
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /template/measurement/{name}', () => {
|
||||
it('deletes the template', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({status: 'OK'});
|
||||
TemplateMeasurementModel.find({name: 'spectrum'}).lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects deleting a template still in use');
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects requests from a write user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/measurement/spectrum',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403
|
||||
})
|
||||
});
|
||||
it('returns 404 for an unknown name', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/measurement/xxx',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 404
|
||||
})
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/template/measurement/spectrum',
|
||||
httpStatus: 401
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
90
src/routes/template.ts
Normal file
90
src/routes/template.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import express from 'express';
|
||||
|
||||
import TemplateValidate from './validate/template';
|
||||
import TemplateTreatmentModel from '../models/treatment_template';
|
||||
import TemplateMeasurementModel from '../models/measurement_template';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/template/:collection(measurements|treatments)', (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
(req.params.collection === 'treatments' ? TemplateTreatmentModel : TemplateMeasurementModel)
|
||||
.find({}).lean().exec((err, data) => {
|
||||
if (err) next (err);
|
||||
res.json(data.map(e => TemplateValidate.output(e)).filter(e => e !== null)); // validate all and filter null values from validation errors
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/template/:collection(measurement|treatment)/:name', (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
(req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel)
|
||||
.findOne({name: req.params.name}).lean().exec((err, data) => {
|
||||
if (err) next (err);
|
||||
if (data) {
|
||||
res.json(TemplateValidate.output(data));
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/template/:collection(measurement|treatment)/:name', (req, res, next) => {
|
||||
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
|
||||
|
||||
const collectionModel = req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel;
|
||||
|
||||
collectionModel.findOne({name: req.params.name}).lean().exec((err, data) => {
|
||||
if (err) next (err);
|
||||
const templateState = data? 'change': 'new';
|
||||
const {error, value: template} = TemplateValidate.input(req.body, templateState);
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (template.hasOwnProperty('name') && template.name !== req.params.name) {
|
||||
collectionModel.find({name: template.name}).lean().exec((err, data) => {
|
||||
if (err) next (err);
|
||||
if (data.length > 0) {
|
||||
res.status(400).json({status: 'Template name already taken'});
|
||||
return;
|
||||
}
|
||||
else {
|
||||
f();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
f();
|
||||
}
|
||||
|
||||
function f() { // to resolve async
|
||||
collectionModel.findOneAndUpdate({name: req.params.name}, template, {new: true, upsert: true}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(TemplateValidate.output(data));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/template/:collection(measurement|treatment)/:name', (req, res, next) => {
|
||||
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
|
||||
|
||||
(req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel)
|
||||
.findOneAndDelete({name: req.params.name}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json({status: 'OK'})
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
626
src/routes/user.spec.ts
Normal file
626
src/routes/user.spec.ts
Normal file
@ -0,0 +1,626 @@
|
||||
import should from 'should/as-function';
|
||||
import UserModel from '../models/user';
|
||||
import TestHelper from "../helpers/test";
|
||||
|
||||
|
||||
describe('/user', () => {
|
||||
let server;
|
||||
before(done => TestHelper.before(done));
|
||||
beforeEach(done => server = TestHelper.beforeEach(server, done));
|
||||
afterEach(done => TestHelper.afterEach(server, done));
|
||||
|
||||
describe('GET /users', () => {
|
||||
it('returns all users', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/users',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
should(res.body).have.lengthOf(json.collections.users.length);
|
||||
should(res.body).matchEach(user => {
|
||||
should(user).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
|
||||
should(user).have.property('_id').be.type('string');
|
||||
should(user).have.property('email').be.type('string');
|
||||
should(user).have.property('name').be.type('string');
|
||||
should(user).have.property('level').be.type('string');
|
||||
should(user).have.property('location').be.type('string');
|
||||
should(user).have.property('device_name').be.type('string');
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects requests from non-admins', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/users',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403
|
||||
});
|
||||
});
|
||||
it('rejects requests from an admin API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/users',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/users',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /user/{name}', () => {
|
||||
it('returns own user details', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('email', 'jane.doe@bosch.com');
|
||||
should(res.body).have.property('name', 'janedoe');
|
||||
should(res.body).have.property('level', 'write');
|
||||
should(res.body).have.property('location', 'Rng');
|
||||
should(res.body).have.property('device_name', 'Alpha I');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns other user details for admin', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/janedoe',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('email', 'jane.doe@bosch.com');
|
||||
should(res.body).have.property('name', 'janedoe');
|
||||
should(res.body).have.property('level', 'write');
|
||||
should(res.body).have.property('location', 'Rng');
|
||||
should(res.body).have.property('device_name', 'Alpha I');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects requests from non-admins for another user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/admin',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403
|
||||
});
|
||||
});
|
||||
it('rejects requests from a user API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects requests from an admin API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/janedoe',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('returns 404 for an unknown user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/unknown',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects requests from an admin API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/janedoe',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /user/{name}', () => {
|
||||
it('returns own user details', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('email', 'jane.doe@bosch.com');
|
||||
should(res.body).have.property('name', 'janedoe');
|
||||
should(res.body).have.property('level', 'write');
|
||||
should(res.body).have.property('location', 'Rng');
|
||||
should(res.body).have.property('device_name', 'Alpha I');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns other user details for admin', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user/janedoe',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('email', 'jane.doe@bosch.com');
|
||||
should(res.body).have.property('name', 'janedoe');
|
||||
should(res.body).have.property('level', 'write');
|
||||
should(res.body).have.property('location', 'Rng');
|
||||
should(res.body).have.property('device_name', 'Alpha I');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('changes user details as given', 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'}
|
||||
}).end(err => {
|
||||
if (err) return done (err);
|
||||
UserModel.find({name: 'adminnew'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'device_name', 'key', '__v');
|
||||
should(data[0]).have.property('_id');
|
||||
should(data[0]).have.property('name', 'adminnew');
|
||||
should(data[0]).have.property('email', 'adminnew@bosch.com');
|
||||
should(data[0]).have.property('pass').not.eql('Abc123##');
|
||||
should(data[0]).have.property('level', 'admin');
|
||||
should(data[0]).have.property('location', 'Abt');
|
||||
should(data[0]).have.property('device_name', 'test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('lets the admin change a user level', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user/janedoe',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {level: 'read'}
|
||||
}).end(err => {
|
||||
if (err) return done (err);
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.property('level', 'read');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('does not change the level', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400, default: false,
|
||||
req: {level: 'read'}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql({status: 'Invalid body format'});
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.property('level', 'write');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects a username already in use', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400, default: false,
|
||||
req: {name: 'janedoe'}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql({status: 'Username already taken'});
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects a username which is in the special names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400, default: false,
|
||||
req: {email: 'j.doe@bosch.com', name: 'passreset', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
||||
res: {status: 'Username already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects invalid user details', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', location: 44, device_name: 'Alpha II'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid email address', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {email: 'john.doe'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid password', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {pass: 'password'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects requests from non-admins for another user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user/admin',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a user API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects requests from an admin API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user/janedoe',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('returns 404 for an unknown user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user/unknown',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 404,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/user/janedoe',
|
||||
httpStatus: 401,
|
||||
req: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /user/{name}', () => {
|
||||
it('deletes own user details', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/user',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql({status: 'OK'});
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('deletes other user details for admin', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/user/janedoe',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql({status: 'OK'});
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects requests from non-admins for another user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/user/admin',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403
|
||||
});
|
||||
});
|
||||
it('rejects requests from a user API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/user',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects requests from an admin API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/user/janedoe',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('returns 404 for an unknown user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/user/unknown',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/user/janedoe',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /user/key', () => {
|
||||
it('returns the right API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/key',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {key: TestHelper.auth.janedoe.key}
|
||||
});
|
||||
});
|
||||
it('rejects requests from an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/key',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects requests from an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/user/key',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /user/new', () => {
|
||||
it('returns the added user data', 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'}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('email', 'john.doe@bosch.com');
|
||||
should(res.body).have.property('name', 'johndoe');
|
||||
should(res.body).have.property('level', 'read');
|
||||
should(res.body).have.property('location', 'Rng');
|
||||
should(res.body).have.property('device_name', 'Alpha II');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('stores the data', 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'}
|
||||
}).end(err => {
|
||||
if (err) return done (err);
|
||||
UserModel.find({name: 'johndoe'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'device_name', 'key', '__v');
|
||||
should(data[0]).have.property('_id');
|
||||
should(data[0]).have.property('name', 'johndoe');
|
||||
should(data[0]).have.property('email', 'john.doe@bosch.com');
|
||||
should(data[0]).have.property('pass').not.eql('Abc123!#');
|
||||
should(data[0]).have.property('level', 'read');
|
||||
should(data[0]).have.property('location', 'Rng');
|
||||
should(data[0]).have.property('device_name', 'Alpha II');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects a username already in use', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400, default: false,
|
||||
req: {email: 'j.doe@bosch.com', name: 'janedoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql({status: 'Username already taken'});
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects a username which is in the special names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400, default: false,
|
||||
req: {email: 'j.doe@bosch.com', name: 'passreset', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
||||
res: {status: 'Username already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects invalid user details', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 44, device_name: 'Alpha II'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid user level', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'xxx', location: 'Rng', device_name: 'Alpha II'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid email address', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {email: 'john.doe', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid password', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'password', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
||||
res: {status: 'Invalid body format'}
|
||||
});
|
||||
});
|
||||
it('rejects requests from non-admins', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403,
|
||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'}
|
||||
});
|
||||
});
|
||||
it('rejects requests from an admin API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401,
|
||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/new',
|
||||
httpStatus: 401,
|
||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /user/passreset', () => {
|
||||
it('returns the ok response', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/passreset',
|
||||
httpStatus: 200,
|
||||
req: {email: 'jane.doe@bosch.com', name: 'janedoe'},
|
||||
res: {status: 'OK'}
|
||||
});
|
||||
});
|
||||
it('returns 404 for wrong username/email combo', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/passreset',
|
||||
httpStatus: 404,
|
||||
req: {email: 'jane.doe@bosch.com', name: 'admin'}
|
||||
});
|
||||
});
|
||||
it('returns 404 for unknown username', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/passreset',
|
||||
httpStatus: 404,
|
||||
req: {email: 'jane.doe@bosch.com', name: 'username'}
|
||||
});
|
||||
});
|
||||
it('changes the user password', done => {
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data: any) => {
|
||||
if (err) return done(err);
|
||||
const oldpass = data[0].pass;
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/user/passreset',
|
||||
httpStatus: 200,
|
||||
req: {email: 'jane.doe@bosch.com', name: 'janedoe'}
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({status: 'OK'});
|
||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data: any) => {
|
||||
if (err) return done(err);
|
||||
should(data[0].pass).not.eql(oldpass);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
173
src/routes/user.ts
Normal file
173
src/routes/user.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
import UserValidate from './validate/user';
|
||||
import UserModel from '../models/user';
|
||||
import mail from '../helpers/mail';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
||||
router.get('/users', (req, res) => {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
|
||||
UserModel.find({}).lean().exec( (err, data:any) => {
|
||||
res.json(data.map(e => UserValidate.output(e)).filter(e => e !== null)); // validate all and filter null values from validation errors
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
||||
req.params.username = req.params[0];
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
let username = req.authDetails.username;
|
||||
if (req.params.username !== undefined) {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
username = req.params.username;
|
||||
}
|
||||
|
||||
UserModel.findOne({name: username}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(UserValidate.output(data)); // validate all and filter null values from validation errors
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
|
||||
req.params.username = req.params[0];
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
let username = req.authDetails.username;
|
||||
if (req.params.username !== undefined) {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
username = req.params.username;
|
||||
}
|
||||
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.hasOwnProperty('pass')) {
|
||||
user.pass = bcrypt.hashSync(user.pass, 10);
|
||||
}
|
||||
|
||||
// check that user does not already exist if new name was specified
|
||||
if (user.hasOwnProperty('name') && user.name !== username) {
|
||||
UserModel.find({name: user.name}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data.length > 0 || UserValidate.isSpecialName(user.name)) {
|
||||
res.status(400).json({status: 'Username already taken'});
|
||||
return;
|
||||
}
|
||||
|
||||
UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(UserValidate.output(data));
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(UserValidate.output(data)); // validate all and filter null values from validation errors
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
||||
req.params.username = req.params[0];
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
let username = req.authDetails.username;
|
||||
if (req.params.username !== undefined) {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
username = req.params.username;
|
||||
}
|
||||
|
||||
UserModel.findOneAndDelete({name: username}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json({status: 'OK'})
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/user/key', (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
UserModel.findOne({name: req.authDetails.username}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
res.json({key: data.key});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/user/new', (req, res, next) => {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
|
||||
// validate input
|
||||
const {error, value: user} = UserValidate.input(req.body, 'new');
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
|
||||
// check that user does not already exist
|
||||
UserModel.find({name: user.name}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data.length > 0 || UserValidate.isSpecialName(user.name)) {
|
||||
res.status(400).json({status: 'Username already taken'});
|
||||
return;
|
||||
}
|
||||
|
||||
user.key = mongoose.Types.ObjectId(); // use object id as unique API key
|
||||
bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing
|
||||
user.pass = hash;
|
||||
new UserModel(user).save((err, data) => { // store user
|
||||
if (err) return next(err);
|
||||
res.json(UserValidate.output(data.toObject()));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/user/passreset', (req, res, next) => {
|
||||
// check if user/email combo exists
|
||||
UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
|
||||
if (err) return next(err);
|
||||
if (data.length === 1) { // it exists
|
||||
const newPass = Math.random().toString(36).substring(2);
|
||||
bcrypt.hash(newPass, 10, (err, hash) => { // password hashing
|
||||
if (err) return next(err);
|
||||
UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}, err => { // write new password
|
||||
if (err) return next(err);
|
||||
mail(data[0].email, 'Your new password for the DFOP database', 'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.<br><br>The DFOP team', err => {
|
||||
if (err) return next(err);
|
||||
res.json({status: 'OK'});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
26
src/routes/validate/id.ts
Normal file
26
src/routes/validate/id.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import joi from '@hapi/joi';
|
||||
|
||||
export default class IdValidate {
|
||||
private static id = joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24);
|
||||
|
||||
static get () {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
static valid (id) {
|
||||
return this.id.validate(id).error === undefined;
|
||||
}
|
||||
|
||||
static parameter () { // :id url parameter
|
||||
return ':id([0-9a-f]{24})';
|
||||
}
|
||||
|
||||
static stringify (data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
|
||||
data[key] = data[key].toString();
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
}
|
82
src/routes/validate/material.ts
Normal file
82
src/routes/validate/material.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import joi from '@hapi/joi';
|
||||
|
||||
import IdValidate from './id';
|
||||
|
||||
export default class MaterialValidate { // validate input for material
|
||||
private static material = {
|
||||
name: joi.string()
|
||||
.max(128),
|
||||
|
||||
supplier: joi.string()
|
||||
.max(128),
|
||||
|
||||
group: joi.string()
|
||||
.max(128),
|
||||
|
||||
mineral: joi.number()
|
||||
.integer()
|
||||
.min(0)
|
||||
.max(100),
|
||||
|
||||
glass_fiber: joi.number()
|
||||
.integer()
|
||||
.min(0)
|
||||
.max(100),
|
||||
|
||||
carbon_fiber: joi.number()
|
||||
.integer()
|
||||
.min(0)
|
||||
.max(100),
|
||||
|
||||
numbers: joi.array()
|
||||
.items(joi.object({
|
||||
color: joi.string()
|
||||
.max(128),
|
||||
number: joi.number()
|
||||
.min(0)
|
||||
}))
|
||||
};
|
||||
|
||||
static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated)
|
||||
if (param === 'new') {
|
||||
return joi.object({
|
||||
name: this.material.name.required(),
|
||||
supplier: this.material.supplier.required(),
|
||||
group: this.material.group.required(),
|
||||
mineral: this.material.mineral.required(),
|
||||
glass_fiber: this.material.glass_fiber.required(),
|
||||
carbon_fiber: this.material.carbon_fiber.required(),
|
||||
numbers: this.material.numbers
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'change') {
|
||||
return joi.object({
|
||||
name: this.material.name,
|
||||
supplier: this.material.supplier,
|
||||
group: this.material.group,
|
||||
mineral: this.material.mineral,
|
||||
glass_fiber: this.material.glass_fiber,
|
||||
carbon_fiber: this.material.carbon_fiber,
|
||||
numbers: this.material.numbers
|
||||
}).validate(data);
|
||||
}
|
||||
else {
|
||||
return{error: 'No parameter specified!', value: {}};
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) { // validate output from database for needed properties, strip everything else
|
||||
data = IdValidate.stringify(data);
|
||||
const {value, error} = joi.object({
|
||||
_id: IdValidate.get(),
|
||||
name: this.material.name,
|
||||
supplier: this.material.supplier,
|
||||
group: this.material.group,
|
||||
mineral: this.material.mineral,
|
||||
glass_fiber: this.material.glass_fiber,
|
||||
carbon_fiber: this.material.carbon_fiber,
|
||||
numbers: this.material.numbers
|
||||
}).validate(data, {stripUnknown: true});
|
||||
return error !== undefined? null : value;
|
||||
}
|
||||
}
|
18
src/routes/validate/note_field.ts
Normal file
18
src/routes/validate/note_field.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import joi from '@hapi/joi';
|
||||
|
||||
export default class NoteFieldValidate {
|
||||
private static note_field = {
|
||||
name: joi.string()
|
||||
.max(128),
|
||||
|
||||
qty: joi.number()
|
||||
};
|
||||
|
||||
static output (data) {
|
||||
const {value, error} = joi.object({
|
||||
name: this.note_field.name,
|
||||
qty: this.note_field.qty
|
||||
}).validate(data, {stripUnknown: true});
|
||||
return error !== undefined? null : value;
|
||||
}
|
||||
}
|
77
src/routes/validate/sample.ts
Normal file
77
src/routes/validate/sample.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import joi from '@hapi/joi';
|
||||
|
||||
import IdValidate from './id';
|
||||
|
||||
export default class SampleValidate {
|
||||
private static sample = {
|
||||
number: joi.string()
|
||||
.max(128),
|
||||
|
||||
color: joi.string()
|
||||
.max(128),
|
||||
|
||||
type: joi.string()
|
||||
.max(128),
|
||||
|
||||
batch: joi.string()
|
||||
.max(128)
|
||||
.allow(''),
|
||||
|
||||
notes: joi.object({
|
||||
comment: joi.string()
|
||||
.max(512),
|
||||
|
||||
sample_references: joi.array()
|
||||
.items(joi.object({
|
||||
id: IdValidate.get(),
|
||||
|
||||
relation: joi.string()
|
||||
.max(128)
|
||||
})),
|
||||
|
||||
custom_fields: joi.object()
|
||||
.pattern(/.*/, joi.alternatives()
|
||||
.try(
|
||||
joi.string().max(128),
|
||||
joi.number(),
|
||||
joi.boolean(),
|
||||
joi.date()
|
||||
)
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated)
|
||||
if (param === 'new') {
|
||||
return joi.object({
|
||||
number: this.sample.number.required(),
|
||||
color: this.sample.color.required(),
|
||||
type: this.sample.type.required(),
|
||||
batch: this.sample.batch.required(),
|
||||
material_id: IdValidate.get().required(),
|
||||
notes: this.sample.notes.required()
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'change') {
|
||||
return{error: 'Not implemented!', value: {}};
|
||||
}
|
||||
else {
|
||||
return{error: 'No parameter specified!', value: {}};
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) {
|
||||
data = IdValidate.stringify(data);
|
||||
const {value, error} = joi.object({
|
||||
_id: IdValidate.get(),
|
||||
number: this.sample.number,
|
||||
color: this.sample.color,
|
||||
type: this.sample.type,
|
||||
batch: this.sample.batch,
|
||||
material_id: IdValidate.get(),
|
||||
note_id: IdValidate.get().allow(null),
|
||||
user_id: IdValidate.get()
|
||||
}).validate(data, {stripUnknown: true});
|
||||
return error !== undefined? null : value;
|
||||
}
|
||||
}
|
59
src/routes/validate/template.ts
Normal file
59
src/routes/validate/template.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import joi from '@hapi/joi';
|
||||
import IdValidate from './id';
|
||||
|
||||
export default class TemplateValidate {
|
||||
private static template = {
|
||||
name: joi.string()
|
||||
.max(128),
|
||||
|
||||
parameters: joi.array()
|
||||
.min(1)
|
||||
.items(
|
||||
joi.object({
|
||||
name: joi.string()
|
||||
.max(128)
|
||||
.required(),
|
||||
|
||||
range: joi.object({
|
||||
values: joi.array()
|
||||
.min(1),
|
||||
|
||||
min: joi.number(),
|
||||
|
||||
max: joi.number()
|
||||
})
|
||||
.oxor('values', 'min')
|
||||
.oxor('values', 'max')
|
||||
.required()
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated)
|
||||
if (param === 'new') {
|
||||
return joi.object({
|
||||
name: this.template.name.required(),
|
||||
parameters: this.template.parameters.required()
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'change') {
|
||||
return joi.object({
|
||||
name: this.template.name,
|
||||
parameters: this.template.parameters
|
||||
}).validate(data);
|
||||
}
|
||||
else {
|
||||
return{error: 'No parameter specified!', value: {}};
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) { // validate output from database for needed properties, strip everything else
|
||||
data = IdValidate.stringify(data);
|
||||
const {value, error} = joi.object({
|
||||
_id: IdValidate.get(),
|
||||
name: this.template.name,
|
||||
parameters: this.template.parameters
|
||||
}).validate(data, {stripUnknown: true});
|
||||
return error !== undefined? null : value;
|
||||
}
|
||||
}
|
87
src/routes/validate/user.ts
Normal file
87
src/routes/validate/user.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import joi from '@hapi/joi';
|
||||
import globals from '../../globals';
|
||||
|
||||
import IdValidate from './id';
|
||||
|
||||
export default class UserValidate { // validate input for user
|
||||
private static user = {
|
||||
name: joi.string()
|
||||
.alphanum()
|
||||
.lowercase()
|
||||
.max(128),
|
||||
|
||||
email: joi.string()
|
||||
.email({minDomainSegments: 2})
|
||||
.lowercase()
|
||||
.max(128),
|
||||
|
||||
pass: joi.string()
|
||||
.pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$'))
|
||||
.max(128),
|
||||
|
||||
level: joi.string()
|
||||
.valid(...globals.levels),
|
||||
|
||||
location: joi.string()
|
||||
.alphanum()
|
||||
.max(128),
|
||||
|
||||
device_name: joi.string()
|
||||
.allow('')
|
||||
.max(128),
|
||||
};
|
||||
|
||||
private static specialUsernames = ['admin', 'user', 'key', 'new', 'passreset']; // names a user cannot take
|
||||
|
||||
static input (data, param) {
|
||||
if (param === 'new') {
|
||||
return joi.object({
|
||||
name: this.user.name.required(),
|
||||
email: this.user.email.required(),
|
||||
pass: this.user.pass.required(),
|
||||
level: this.user.level.required(),
|
||||
location: this.user.location.required(),
|
||||
device_name: this.user.device_name.required()
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'change') {
|
||||
return joi.object({
|
||||
name: this.user.name,
|
||||
email: this.user.email,
|
||||
pass: this.user.pass,
|
||||
location: this.user.location,
|
||||
device_name: this.user.device_name
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'changeadmin') {
|
||||
return joi.object({
|
||||
name: this.user.name,
|
||||
email: this.user.email,
|
||||
pass: this.user.pass,
|
||||
level: this.user.level,
|
||||
location: this.user.location,
|
||||
device_name: this.user.device_name
|
||||
}).validate(data);
|
||||
}
|
||||
else {
|
||||
return{error: 'No parameter specified!', value: {}};
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) { // validate output from database for needed properties, strip everything else
|
||||
data = IdValidate.stringify(data);
|
||||
const {value, error} = joi.object({
|
||||
_id: IdValidate.get(),
|
||||
name: this.user.name,
|
||||
email: this.user.email,
|
||||
level: this.user.level,
|
||||
location: this.user.location,
|
||||
device_name: this.user.device_name
|
||||
}).validate(data, {stripUnknown: true});
|
||||
return error !== undefined? null : value;
|
||||
}
|
||||
|
||||
static isSpecialName (name) { // true if name belongs to special names
|
||||
return this.specialUsernames.indexOf(name) > -1;
|
||||
}
|
||||
}
|
281
src/test/db.json
Normal file
281
src/test/db.json
Normal file
@ -0,0 +1,281 @@
|
||||
{
|
||||
"collections": {
|
||||
"samples": [
|
||||
{
|
||||
"_id": {"$oid":"400000000000000000000001"},
|
||||
"number": "1",
|
||||
"type": "granulate",
|
||||
"color": "black",
|
||||
"batch": "",
|
||||
"validated": true,
|
||||
"material_id": {"$oid":"100000000000000000000004"},
|
||||
"note_id": null,
|
||||
"user_id": {"$oid":"000000000000000000000002"},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"400000000000000000000002"},
|
||||
"number": "21",
|
||||
"type": "granulate",
|
||||
"color": "natural",
|
||||
"batch": "1560237365",
|
||||
"validated": true,
|
||||
"material_id": {"$oid":"100000000000000000000001"},
|
||||
"note_id": {"$oid":"500000000000000000000001"},
|
||||
"user_id": {"$oid":"000000000000000000000002"},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"400000000000000000000003"},
|
||||
"number": "33",
|
||||
"type": "part",
|
||||
"color": "black",
|
||||
"batch": "1704-005",
|
||||
"validated": false,
|
||||
"material_id": {"$oid":"100000000000000000000005"},
|
||||
"note_id": {"$oid":"500000000000000000000002"},
|
||||
"user_id": {"$oid":"000000000000000000000003"},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"400000000000000000000004"},
|
||||
"number": "32",
|
||||
"type": "granulate",
|
||||
"color": "black",
|
||||
"batch": "1653000308",
|
||||
"validated": false,
|
||||
"material_id": {"$oid":"100000000000000000000005"},
|
||||
"note_id": {"$oid":"500000000000000000000003"},
|
||||
"user_id": {"$oid":"000000000000000000000003"},
|
||||
"__v": 0
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
{
|
||||
"_id": {"$oid":"500000000000000000000001"},
|
||||
"comment": "Stoff gesperrt",
|
||||
"sample_references": [],
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"500000000000000000000002"},
|
||||
"comment": "",
|
||||
"sample_references": [{
|
||||
"id": "400000000000000000000004",
|
||||
"relation": "granulate to sample"
|
||||
}],
|
||||
"custom_fields": {
|
||||
"not allowed for new applications": true
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"500000000000000000000003"},
|
||||
"comment": "",
|
||||
"sample_references": [{
|
||||
"id": "400000000000000000000003",
|
||||
"relation": "part to sample"
|
||||
}],
|
||||
"custom_fields": {
|
||||
"not allowed for new applications": true
|
||||
},
|
||||
"__v": 0
|
||||
}
|
||||
],
|
||||
"note_fields": [
|
||||
{
|
||||
"_id": {"$oid":"600000000000000000000001"},
|
||||
"name": "not allowed for new applications",
|
||||
"qty": 2,
|
||||
"__v": 0
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"_id": {"$oid":"100000000000000000000001"},
|
||||
"name": "Stanyl TW 200 F8",
|
||||
"supplier": "DSM",
|
||||
"group": "PA46",
|
||||
"mineral": 0,
|
||||
"glass_fiber": 40,
|
||||
"carbon_fiber": 0,
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": 5514263423
|
||||
},
|
||||
{
|
||||
"color": "natural",
|
||||
"number": 5514263422
|
||||
}
|
||||
],
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"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
|
||||
}
|
||||
],
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"100000000000000000000003"},
|
||||
"name": "PA GF 50 black (2706)",
|
||||
"supplier": "Akro-Plastic",
|
||||
"group": "PA66+PA6I/6T",
|
||||
"mineral": 0,
|
||||
"glass_fiber": 0,
|
||||
"carbon_fiber": 0,
|
||||
"numbers": [
|
||||
],
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"100000000000000000000004"},
|
||||
"name": "Schulamid 66 GF 25 H",
|
||||
"supplier": "Schulmann",
|
||||
"group": "PA66",
|
||||
"mineral": 0,
|
||||
"glass_fiber": 25,
|
||||
"carbon_fiber": 0,
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": 5513933405
|
||||
}
|
||||
],
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"100000000000000000000005"},
|
||||
"name": "Amodel A 1133 HS",
|
||||
"supplier": "Solvay",
|
||||
"group": "PPA",
|
||||
"mineral": 0,
|
||||
"glass_fiber": 33,
|
||||
"carbon_fiber": 0,
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": 5514262406
|
||||
}
|
||||
],
|
||||
"__v": 0
|
||||
}
|
||||
],
|
||||
"treatment_templates": [
|
||||
{
|
||||
"_id": {"$oid":"200000000000000000000001"},
|
||||
"name": "heat treatment",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "material",
|
||||
"range": {
|
||||
"values": [
|
||||
"copper",
|
||||
"hot air"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "weeks",
|
||||
"range": {
|
||||
"min": 1,
|
||||
"max": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"200000000000000000000002"},
|
||||
"name": "heat treatment 2",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "material",
|
||||
"range": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"measurement_templates": [
|
||||
{
|
||||
"_id": {"$oid":"300000000000000000000001"},
|
||||
"name": "spectrum",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "dpt",
|
||||
"range": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"300000000000000000000002"},
|
||||
"name": "kf",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "weight %",
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 1.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "standard deviation",
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 0.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"_id": {"$oid":"000000000000000000000001"},
|
||||
"email": "user@bosch.com",
|
||||
"name": "user",
|
||||
"pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
|
||||
"level": "read",
|
||||
"location": "Rng",
|
||||
"device_name": "Alpha I",
|
||||
"key": "000000000000000000001001",
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"000000000000000000000002"},
|
||||
"email": "jane.doe@bosch.com",
|
||||
"name": "janedoe",
|
||||
"pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
|
||||
"level": "write",
|
||||
"location": "Rng",
|
||||
"device_name": "Alpha I",
|
||||
"key": "000000000000000000001002",
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"000000000000000000000003"},
|
||||
"email": "a.d.m.i.n@bosch.com",
|
||||
"name": "admin",
|
||||
"pass": "$2a$10$i872o3qR5V3JnbDArD8Z.eDo.BNPDBaR7dUX9KSEtl9pUjLyucy2K",
|
||||
"level": "admin",
|
||||
"location": "Rng",
|
||||
"device_name": "",
|
||||
"key": "000000000000000000001003",
|
||||
"__v": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
201
static/img/bosch-logo.svg
Normal file
201
static/img/bosch-logo.svg
Normal file
@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="bosch-lifeclip" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 435 155"
|
||||
style="enable-background:new 0 0 435 155;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.anker{fill:#606061;}
|
||||
.bosch{fill-rule:evenodd;clip-rule:evenodd;fill:#EA0016;}
|
||||
.claim{fill:#000000;}
|
||||
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||
.st1{fill:url(#SVGID_1_);}
|
||||
.st2{fill:#942432;}
|
||||
.st3{fill:#B22739;}
|
||||
.st4{fill:#931915;}
|
||||
.st5{fill:#AF1A19;}
|
||||
.st6{fill:#D5151A;}
|
||||
.st7{fill:url(#SVGID_2_);}
|
||||
.st8{fill:url(#SVGID_3_);}
|
||||
.st9{fill:url(#SVGID_4_);}
|
||||
.st10{fill:url(#SVGID_5_);}
|
||||
.st11{fill:url(#SVGID_6_);}
|
||||
.st12{fill:#253783;}
|
||||
.st13{fill:url(#SVGID_7_);}
|
||||
.st14{fill:url(#SVGID_8_);}
|
||||
.st15{fill:url(#SVGID_9_);}
|
||||
.st16{fill:url(#SVGID_10_);}
|
||||
.st17{fill:url(#SVGID_11_);}
|
||||
.st18{fill:url(#SVGID_12_);}
|
||||
.st19{fill:#159A39;}
|
||||
.st20{fill:url(#SVGID_13_);}
|
||||
.st21{fill:url(#SVGID_14_);}
|
||||
.st22{fill:url(#SVGID_15_);}
|
||||
.st23{fill:url(#SVGID_16_);}
|
||||
.st24{fill:url(#SVGID_17_);}
|
||||
.st25{fill:url(#SVGID_18_);}
|
||||
.st26{fill:url(#SVGID_19_);}
|
||||
.st27{fill:url(#SVGID_20_);}
|
||||
.st28{fill:url(#SVGID_21_);}
|
||||
.st29{fill:url(#SVGID_22_);}
|
||||
.st30{fill:url(#SVGID_23_);}
|
||||
.st31{fill:url(#SVGID_24_);}
|
||||
.st32{fill:url(#SVGID_25_);}
|
||||
.st33{fill:url(#SVGID_26_);}
|
||||
.st34{fill:url(#SVGID_27_);}
|
||||
.st35{fill:url(#SVGID_28_);}
|
||||
.st36{fill:url(#SVGID_29_);}
|
||||
.st37{fill:url(#SVGID_30_);}
|
||||
.st38{fill:url(#SVGID_31_);}
|
||||
.st39{fill:url(#SVGID_32_);}
|
||||
.st40{fill:url(#SVGID_33_);}
|
||||
.st41{fill:url(#SVGID_34_);}
|
||||
.st42{fill:url(#SVGID_35_);}
|
||||
.st43{fill:url(#SVGID_36_);}
|
||||
.st44{fill:url(#SVGID_37_);}
|
||||
.st45{fill:url(#SVGID_38_);}
|
||||
.st46{fill:url(#SVGID_39_);}
|
||||
.st47{fill:url(#SVGID_40_);}
|
||||
.st48{fill:url(#SVGID_41_);}
|
||||
.st49{fill:url(#SVGID_42_);}
|
||||
.st50{fill:url(#SVGID_43_);}
|
||||
.st51{fill:url(#SVGID_44_);}
|
||||
.st52{fill:url(#SVGID_45_);}
|
||||
.st53{fill:url(#SVGID_46_);}
|
||||
.st54{fill:url(#SVGID_47_);}
|
||||
.st55{fill:url(#SVGID_48_);}
|
||||
.st56{fill:url(#SVGID_49_);}
|
||||
.st57{fill:url(#SVGID_50_);}
|
||||
.st58{fill:url(#SVGID_51_);}
|
||||
.st59{fill:url(#SVGID_52_);}
|
||||
.st60{fill:url(#SVGID_53_);}
|
||||
.st61{fill:url(#SVGID_54_);}
|
||||
.st62{fill:url(#SVGID_55_);}
|
||||
.st63{fill:url(#SVGID_56_);}
|
||||
</style>
|
||||
<g id="box">
|
||||
<g id="claim-english">
|
||||
<path class="claim" d="M147.66699,107.6748v27.80957h-3.22852V107.6748H147.66699z"/>
|
||||
<path class="claim" d="M157.896,115.14258v3.11133c1.24463-2.29492,3.30615-3.46191,6.18408-3.46191
|
||||
c4.35645,0,6.729,2.83984,6.729,8.09082v12.60156h-3.22852v-12.83496c0-3.50098-1.32227-5.05664-4.31689-5.05664
|
||||
c-3.26758,0-5.36768,2.13965-5.36768,5.44531v12.44629h-3.22803v-20.3418H157.896z"/>
|
||||
<path class="claim" d="M178.39355,115.14258l5.44531,17.07422l5.32861-17.07422h3.34473l-6.84521,20.3418h-3.81201
|
||||
l-6.96191-20.3418H178.39355z"/>
|
||||
<path class="claim" d="M213.16455,132.25586c-1.8667,2.68359-4.47266,3.57812-7.73975,3.57812
|
||||
c-6.45654,0-10.07373-4.23926-10.07373-10.46191c0-6.2627,3.77246-10.58008,9.25684-10.58008
|
||||
c5.40625,0,8.7124,3.8125,8.7124,10.11328v1.0498H198.6958c0.07764,2.91699,0.73926,4.55078,2.13916,5.83398
|
||||
c1.05029,0.93359,2.41162,1.36133,4.51172,1.36133c2.25586,0,3.88965-0.62207,5.44531-2.7998L213.16455,132.25586z
|
||||
M210.01416,123.34961c-0.42773-4.16211-1.9834-5.87305-5.36719-5.87305c-3.22852,0-5.09521,1.90527-5.83447,5.87305H210.01416z"
|
||||
/>
|
||||
<path class="claim" d="M222.69336,115.14258v3.11133c1.24414-2.29492,3.30566-3.46191,6.18359-3.46191
|
||||
c4.35645,0,6.72852,2.83984,6.72852,8.09082v12.60156h-3.22754v-12.83496c0-3.50098-1.32227-5.05664-4.31738-5.05664
|
||||
c-3.26758,0-5.36719,2.13965-5.36719,5.44531v12.44629h-3.22852v-20.3418H222.69336z"/>
|
||||
<path class="claim" d="M243.9668,115.14258v-5.29004l3.22754-0.77734v6.06738h5.13477v2.68359h-5.13477v12.40723
|
||||
c0,1.9834,0.73926,2.91699,2.33398,2.91699c1.0498,0,1.82812-0.27148,2.76172-0.97168l1.24414,2.2168
|
||||
c-1.36133,1.0498-2.52734,1.43848-4.2002,1.43848c-3.30566,0-5.36719-1.90527-5.36719-4.97852v-13.0293h-3.1123v-2.68359H243.9668
|
||||
z"/>
|
||||
<path class="claim" d="M274.61426,132.25586c-1.86719,2.68359-4.47266,3.57812-7.74023,3.57812
|
||||
c-6.45605,0-10.07324-4.23926-10.07324-10.46191c0-6.2627,3.77246-10.58008,9.25684-10.58008
|
||||
c5.40625,0,8.71191,3.8125,8.71191,10.11328v1.0498h-14.62402c0.07812,2.91699,0.73926,4.55078,2.13965,5.83398
|
||||
c1.0498,0.93359,2.41113,1.36133,4.51172,1.36133c2.25586,0,3.88867-0.62207,5.44531-2.7998L274.61426,132.25586z
|
||||
M271.46387,123.34961c-0.42773-4.16211-1.9834-5.87305-5.36719-5.87305c-3.22852,0-5.0957,1.90527-5.83398,5.87305H271.46387z"/>
|
||||
<path class="claim" d="M294.64453,132.2168c-1.51758,2.37305-3.8125,3.61719-6.72949,3.61719
|
||||
c-5.28906,0-8.71191-4.16113-8.71191-10.61816c0-6.30078,3.46191-10.42383,8.67383-10.42383
|
||||
c2.87793,0,5.21094,1.28418,6.76758,3.69531v-12.29102h3.22754v29.28809h-3.22754V132.2168z M282.54785,125.41113
|
||||
c0,5.13379,1.94434,7.73926,5.83398,7.73926c4.08398,0,6.37891-2.83887,6.37891-7.81738c0-4.8623-2.37207-7.85645-6.26172-7.85645
|
||||
C284.6875,117.47656,282.54785,120.35449,282.54785,125.41113z"/>
|
||||
<path class="claim" d="M318.79688,115.14258v-2.83984c0-4.16113,2.2168-6.53418,6.02832-6.53418
|
||||
c1.36133,0,2.4502,0.27246,3.65625,0.93359l-0.81738,2.48926c-1.20508-0.58301-1.75-0.73926-2.68359-0.73926
|
||||
c-1.86719,0-2.95605,1.32324-2.95605,3.50098v3.18945h4.66797v2.68359h-4.66797v17.6582h-3.22754v-17.6582h-2.91797v-2.68359
|
||||
H318.79688z"/>
|
||||
<path class="claim" d="M348.08398,125.29395c0,6.14551-4.00586,10.54004-9.64551,10.54004
|
||||
c-5.52344,0-9.56836-4.43359-9.56836-10.50098c0-6.14551,4.00586-10.54102,9.68457-10.54102
|
||||
C344.07812,114.79199,348.08398,119.22656,348.08398,125.29395z M332.21484,125.37207c0,4.78418,2.41113,7.77832,6.22363,7.77832
|
||||
c3.85059,0,6.30078-3.0332,6.30078-7.81738c0-4.74512-2.4502-7.85645-6.18457-7.85645
|
||||
C334.62598,117.47656,332.21484,120.43262,332.21484,125.37207z"/>
|
||||
<path class="claim" d="M356.05664,115.14258v3.2666c1.32227-2.4502,3.07227-3.61719,5.44531-3.61719
|
||||
c1.24414,0,2.2168,0.2334,3.30566,0.81738l-1.32227,2.72266c-0.89453-0.4668-1.43848-0.62207-2.37207-0.62207
|
||||
c-3.15039,0-5.05664,2.52734-5.05664,6.61133v11.16309h-3.22852v-20.3418H356.05664z"/>
|
||||
<path class="claim" d="M387.52148,134.8623c-0.97266,0.58301-1.98438,0.93359-3.5791,0.93359
|
||||
c-2.95508,0-4.93945-1.32324-4.93945-5.36816v-24.23145h3.22852v24.50391c0,1.90625,1.01172,2.41113,2.2168,2.41113
|
||||
c0.93359,0,1.51758-0.27148,2.02246-0.66113L387.52148,134.8623z"/>
|
||||
<path class="claim" d="M395.96094,108.80273c0,1.16699-0.97266,2.13867-2.13965,2.13867s-2.13867-0.97168-2.13867-2.13867
|
||||
s0.97168-2.13965,2.13867-2.13965S395.96094,107.63574,395.96094,108.80273z M395.49414,115.14258v20.3418h-3.22852v-20.3418
|
||||
H395.49414z"/>
|
||||
<path class="claim" d="M403.93359,115.14258v-2.83984c0-4.16113,2.2168-6.53418,6.02832-6.53418
|
||||
c1.36133,0,2.4502,0.27246,3.65625,0.93359l-0.81738,2.48926c-1.20508-0.58301-1.75-0.73926-2.68359-0.73926
|
||||
c-1.86719,0-2.95605,1.32324-2.95605,3.50098v3.18945h4.66797v2.68359h-4.66797v17.6582h-3.22754v-17.6582h-2.91797v-2.68359
|
||||
H403.93359z"/>
|
||||
<path class="claim" d="M431.89844,132.25586c-1.86719,2.68359-4.47266,3.57812-7.74023,3.57812
|
||||
c-6.45605,0-10.07324-4.23926-10.07324-10.46191c0-6.2627,3.77246-10.58008,9.25684-10.58008
|
||||
c5.40625,0,8.71191,3.8125,8.71191,10.11328v1.0498h-14.62402c0.07812,2.91699,0.73926,4.55078,2.13965,5.83398
|
||||
c1.0498,0.93359,2.41113,1.36133,4.51172,1.36133c2.25586,0,3.88965-0.62207,5.44531-2.7998L431.89844,132.25586z
|
||||
M428.74805,123.34961c-0.42773-4.16211-1.9834-5.87305-5.36719-5.87305c-3.22852,0-5.0957,1.90527-5.83398,5.87305H428.74805z"/>
|
||||
</g>
|
||||
<g id="bosch">
|
||||
<g>
|
||||
<path class="bosch" d="M185.19998,46.7c0,0,8.79999-3,8.79999-13c0-11.7-8.29999-17.5-19.70001-17.5h-29.89996v63.59999h32.5
|
||||
c10,0,19.79999-7,19.79999-17.7C196.69998,49.39999,185.19998,46.8,185.19998,46.7z M160,29.39999h11.60001
|
||||
c3.60001,0,6,2.39999,6,6c0,2.8-2.20001,5.8-6.29999,5.8h-11.39999L160,29.39999L160,29.39999z M171.69998,66.5h-11.60001V54
|
||||
h11.30002c5.70001,0,8.39999,2.5,8.39999,6.2C179.79999,64.8,176.39999,66.5,171.69998,66.5z"/>
|
||||
<path class="bosch" d="M231.10001,14.60001c-18.39999,0-29.20001,14.7-29.20001,33.3c0,18.7,10.79999,33.3,29.20001,33.3
|
||||
c18.5,0,29.20001-14.60001,29.20001-33.3C260.29999,29.3,249.60001,14.60001,231.10001,14.60001z M231.10001,66
|
||||
c-9,0-13.5-8.10001-13.5-18.10001s4.5-18,13.5-18s13.60001,8.10001,13.60001,18C244.69998,58,240.10001,66,231.10001,66z"/>
|
||||
<path class="bosch" d="M294.19998,41.2l-2.20001-0.5c-5.39999-1.10001-9.70001-2.5-9.70001-6.39999
|
||||
c0-4.2,4.10001-5.89999,7.70001-5.89999c5.29999,0,10,2.60001,13,5.89999l9.89999-9.8c-4.5-5.10001-11.79999-10-23.20001-10
|
||||
c-13.39999,0-23.60001,7.5-23.60001,20c0,11.39999,8.20001,17,18.20001,19.10001l2.20001,0.5c8.29999,1.7,11.39999,3,11.39999,7
|
||||
c0,3.8-3.39999,6.3-8.60001,6.3c-6.20001,0-11.79999-2.7-16.10001-8.2l-10.10001,10
|
||||
c5.60001,6.7,12.70001,11.89999,26.39999,11.89999c11.89999,0,24.60001-6.8,24.60001-20.7
|
||||
C314.29999,45.89999,303.29999,43.10001,294.19998,41.2z"/>
|
||||
<path class="bosch" d="M349.69998,66c-7,0-14.29999-5.8-14.29999-18.5c0-11.3,6.79999-17.60001,13.89999-17.60001
|
||||
c5.60001,0,8.89999,2.60001,11.5,7.10001l12.79999-8.5c-6.39999-9.7-14-13.8-24.5-13.8
|
||||
c-19.20001,0-29.60001,14.89999-29.60001,32.90001c0,18.89999,11.5,33.7,29.39999,33.7
|
||||
c12.60001,0,18.60001-4.39999,25.10001-13.8l-12.89999-8.7C358.5,63,355.69998,66,349.69998,66z"/>
|
||||
<polygon class="bosch" points="416.30002,16.2 416.30002,39.60001 396.99997,39.60001 396.99997,16.2 380.29999,16.2
|
||||
380.29999,79.8 396.99997,79.8 396.99997,54.7 416.30002,54.7 416.30002,79.8 432.99997,79.8 432.99997,16.2 "/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="bosch" d="M185.19998,46.7c0,0,8.79999-3,8.79999-13c0-11.7-8.29999-17.5-19.70001-17.5h-29.89996v63.59999h32.5
|
||||
c10,0,19.79999-7,19.79999-17.7C196.69998,49.39999,185.19998,46.8,185.19998,46.7z M160,29.39999h11.60001
|
||||
c3.60001,0,6,2.39999,6,6c0,2.8-2.20001,5.8-6.29999,5.8h-11.39999L160,29.39999L160,29.39999z M171.69998,66.5h-11.60001V54
|
||||
h11.30002c5.70001,0,8.39999,2.5,8.39999,6.2C179.79999,64.8,176.39999,66.5,171.69998,66.5z"/>
|
||||
<path class="bosch" d="M231.10001,14.60001c-18.39999,0-29.20001,14.7-29.20001,33.3c0,18.7,10.79999,33.3,29.20001,33.3
|
||||
c18.5,0,29.20001-14.60001,29.20001-33.3C260.29999,29.3,249.60001,14.60001,231.10001,14.60001z M231.10001,66
|
||||
c-9,0-13.5-8.10001-13.5-18.10001s4.5-18,13.5-18s13.60001,8.10001,13.60001,18C244.69998,58,240.10001,66,231.10001,66z"/>
|
||||
<path class="bosch" d="M294.19998,41.2l-2.20001-0.5c-5.39999-1.10001-9.70001-2.5-9.70001-6.39999
|
||||
c0-4.2,4.10001-5.89999,7.70001-5.89999c5.29999,0,10,2.60001,13,5.89999l9.89999-9.8c-4.5-5.10001-11.79999-10-23.20001-10
|
||||
c-13.39999,0-23.60001,7.5-23.60001,20c0,11.39999,8.20001,17,18.20001,19.10001l2.20001,0.5c8.29999,1.7,11.39999,3,11.39999,7
|
||||
c0,3.8-3.39999,6.3-8.60001,6.3c-6.20001,0-11.79999-2.7-16.10001-8.2l-10.10001,10
|
||||
c5.60001,6.7,12.70001,11.89999,26.39999,11.89999c11.89999,0,24.60001-6.8,24.60001-20.7
|
||||
C314.29999,45.89999,303.29999,43.10001,294.19998,41.2z"/>
|
||||
<path class="bosch" d="M349.69998,66c-7,0-14.29999-5.8-14.29999-18.5c0-11.3,6.79999-17.60001,13.89999-17.60001
|
||||
c5.60001,0,8.89999,2.60001,11.5,7.10001l12.79999-8.5c-6.39999-9.7-14-13.8-24.5-13.8
|
||||
c-19.20001,0-29.60001,14.89999-29.60001,32.90001c0,18.89999,11.5,33.7,29.39999,33.7
|
||||
c12.60001,0,18.60001-4.39999,25.10001-13.8l-12.89999-8.7C358.5,63,355.69998,66,349.69998,66z"/>
|
||||
<polygon class="bosch" points="416.30002,16.2 416.30002,39.60001 396.99997,39.60001 396.99997,16.2 380.29999,16.2
|
||||
380.29999,79.8 396.99997,79.8 396.99997,54.7 416.30002,54.7 416.30002,79.8 432.99997,79.8 432.99997,16.2 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="anker">
|
||||
<g>
|
||||
<path class="anker" d="M48.2,0C21.59999,0,0,21.60001,0,48.2s21.60001,48.2,48.2,48.2s48.2-21.60001,48.2-48.2S74.79999,0,48.2,0
|
||||
z M48.2,91.89999c-24.10001,0-43.7-19.60001-43.7-43.7S24.10001,4.5,48.2,4.5s43.7,19.60001,43.7,43.7
|
||||
S72.29999,91.89999,48.2,91.89999z"/>
|
||||
<path class="anker" d="M68.09999,18.10001h-3.3v16.5H31.69998v-16.5h-3.39999c-9.7,6.5-16.2,17.5-16.2,30.10001
|
||||
s6.5,23.60001,16.2,30.10001h3.39999v-16.5h33.10001v16.5h3.3c9.8-6.5,16.2-17.5,16.2-30.10001
|
||||
S77.89999,24.60001,68.09999,18.10001z M27.09999,71.8c-6.7-5.89999-10.60001-14.39999-10.60001-23.60001
|
||||
c0-9.2,3.89999-17.7,10.60001-23.60001V71.8z M64.79999,57.2H31.69998V39.09999h33.10001V57.2z M69.29999,71.7v-10l0,0V34.59999
|
||||
l0,0v-10c6.60001,5.89999,10.5,14.39999,10.5,23.5C79.79999,57.3,75.89999,65.8,69.29999,71.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="anker" d="M48.2,0C21.59999,0,0,21.60001,0,48.2s21.60001,48.2,48.2,48.2s48.2-21.60001,48.2-48.2S74.79999,0,48.2,0
|
||||
z M48.2,91.89999c-24.10001,0-43.7-19.60001-43.7-43.7S24.10001,4.5,48.2,4.5s43.7,19.60001,43.7,43.7
|
||||
S72.29999,91.89999,48.2,91.89999z"/>
|
||||
<path class="anker" d="M68.09999,18.10001h-3.3v16.5H31.69998v-16.5h-3.39999c-9.7,6.5-16.2,17.5-16.2,30.10001
|
||||
s6.5,23.60001,16.2,30.10001h3.39999v-16.5h33.10001v16.5h3.3c9.8-6.5,16.2-17.5,16.2-30.10001
|
||||
S77.89999,24.60001,68.09999,18.10001z M27.09999,71.8c-6.7-5.89999-10.60001-14.39999-10.60001-23.60001
|
||||
c0-9.2,3.89999-17.7,10.60001-23.60001V71.8z M64.79999,57.2H31.69998V39.09999h33.10001V57.2z M69.29999,71.7v-10l0,0V34.59999
|
||||
l0,0v-10c6.60001,5.89999,10.5,14.39999,10.5,23.5C79.79999,57.3,75.89999,65.8,69.29999,71.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
306
static/styles/swagger.css
Normal file
306
static/styles/swagger.css
Normal file
File diff suppressed because one or more lines are too long
@ -4,13 +4,21 @@
|
||||
"target": "es5",
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"incremental": true,
|
||||
"diagnostics": true,
|
||||
"typeRoots": [
|
||||
"src/customTypings",
|
||||
"node_modules/@types"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"./node_modules/@types/node/index.d.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
"src/**/*.ts",
|
||||
"src/**/*.json"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
Reference in New Issue
Block a user