From 600385cede329d3d6f0d09b76b4ad7b6a2e87048 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Wed, 29 Apr 2020 12:10:27 +0200 Subject: [PATCH] added /materials route --- .idea/inspectionProfiles/Project_Default.xml | 1 + oas/oas.yaml => api/api.yaml | 0 {oas => api}/condition.yaml | 38 +- api/material.yaml | 121 ++++++ {oas => api}/measurement.yaml | 38 +- {oas => api}/model.yaml | 34 +- {oas => api}/others.yaml | 6 +- {oas => api}/parameters.yaml | 1 + {oas => api}/responses.yaml | 0 {oas => api}/sample.yaml | 48 +-- {oas => api}/schemas.yaml | 94 ++--- {oas => api}/template.yaml | 88 ++--- {oas => api}/user.yaml | 100 ++--- oas/material.yaml | 67 ---- src/helpers/test.ts | 5 +- src/index.ts | 13 +- src/models/material.ts | 16 + src/routes/material.spec.ts | 387 +++++++++++++++++++ src/routes/material.ts | 59 +++ src/routes/user.spec.ts | 102 +++-- src/routes/user.ts | 3 + src/routes/validate/id.ts | 17 + src/routes/validate/material.ts | 82 ++++ src/routes/validate/user.ts | 31 +- src/test/db.json | 69 +++- static/styles/swagger.css | 0 26 files changed, 1081 insertions(+), 339 deletions(-) rename oas/oas.yaml => api/api.yaml (100%) rename {oas => api}/condition.yaml (51%) create mode 100644 api/material.yaml rename {oas => api}/measurement.yaml (52%) rename {oas => api}/model.yaml (54%) rename {oas => api}/others.yaml (85%) rename {oas => api}/parameters.yaml (79%) rename {oas => api}/responses.yaml (100%) rename {oas => api}/sample.yaml (59%) rename {oas => api}/schemas.yaml (55%) rename {oas => api}/template.yaml (66%) rename {oas => api}/user.yaml (60%) delete mode 100644 oas/material.yaml create mode 100644 src/models/material.ts create mode 100644 src/routes/material.spec.ts create mode 100644 src/routes/material.ts create mode 100644 src/routes/validate/id.ts create mode 100644 src/routes/validate/material.ts create mode 100644 static/styles/swagger.css diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index c947305..7e46df7 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,5 +2,6 @@ \ No newline at end of file diff --git a/oas/oas.yaml b/api/api.yaml similarity index 100% rename from oas/oas.yaml rename to api/api.yaml diff --git a/oas/condition.yaml b/api/condition.yaml similarity index 51% rename from oas/condition.yaml rename to api/condition.yaml index cca8ca6..5efa2ac 100644 --- a/oas/condition.yaml +++ b/api/condition.yaml @@ -1,6 +1,6 @@ /condition/{id}: parameters: - - $ref: 'oas.yaml#/components/parameters/Id' + - $ref: 'api.yaml#/components/parameters/Id' get: summary: TODO condition by id description: 'Auth: all, levels: read, write, maintain, dev, admin' @@ -12,15 +12,15 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Condition' + $ref: 'api.yaml#/components/schemas/Condition' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: TODO add/change condition description: 'Auth: basic, levels: write, maintain, dev, admin' @@ -33,24 +33,24 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Condition' + $ref: 'api.yaml#/components/schemas/Condition' responses: 200: description: condition details content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Condition' + $ref: 'api.yaml#/components/schemas/Condition' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: TODO delete condition description: 'Auth: basic, levels: write, maintain, dev, admin' @@ -60,14 +60,14 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file + $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/api/material.yaml b/api/material.yaml new file mode 100644 index 0000000..c32c1ab --- /dev/null +++ b/api/material.yaml @@ -0,0 +1,121 @@ +/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: TODO 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: TODO delete material + description: 'Auth: basic, levels: write, maintain, dev, admin' + tags: + - /material + 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' + +/material/new: + post: + summary: TODO 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' \ No newline at end of file diff --git a/oas/measurement.yaml b/api/measurement.yaml similarity index 52% rename from oas/measurement.yaml rename to api/measurement.yaml index 0b4d5b2..0f86047 100644 --- a/oas/measurement.yaml +++ b/api/measurement.yaml @@ -1,6 +1,6 @@ /measurement/{id}: parameters: - - $ref: 'oas.yaml#/components/parameters/Id' + - $ref: 'api.yaml#/components/parameters/Id' get: summary: TODO measurement values by id description: 'Auth: all, levels: read, write, maintain, dev, admin' @@ -12,15 +12,15 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Measurement' + $ref: 'api.yaml#/components/schemas/Measurement' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: TODO add/change measurement description: 'Auth: basic, levels: write, maintain, dev, admin' @@ -33,24 +33,24 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Measurement' + $ref: 'api.yaml#/components/schemas/Measurement' responses: 200: description: measurement details content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Measurement' + $ref: 'api.yaml#/components/schemas/Measurement' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: TODO delete measurement description: 'Auth: basic, levels: write, maintain, dev, admin' @@ -60,14 +60,14 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file + $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/oas/model.yaml b/api/model.yaml similarity index 54% rename from oas/model.yaml rename to api/model.yaml index 24df9af..f9c3d72 100644 --- a/oas/model.yaml +++ b/api/model.yaml @@ -1,6 +1,6 @@ /model/{name}: parameters: - - $ref: 'oas.yaml#/components/parameters/Name' + - $ref: 'api.yaml#/components/parameters/Name' get: summary: TODO get model data by name description: 'Auth: all, levels: dev, admin' @@ -15,13 +15,13 @@ type: string format: binary 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: TODO add/replace model data by name description: 'Auth: all, levels: dev, admin' @@ -37,17 +37,17 @@ format: binary responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: TODO delete model data description: 'Auth: basic, levels: dev, admin' @@ -57,14 +57,14 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file + $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/oas/others.yaml b/api/others.yaml similarity index 85% rename from oas/others.yaml rename to api/others.yaml index c543797..a953bf8 100644 --- a/oas/others.yaml +++ b/api/others.yaml @@ -16,7 +16,7 @@ type: string example: 'API server up and running!' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /authorized: get: @@ -38,6 +38,6 @@ type: string example: 'basic' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 500: - $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file + $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/oas/parameters.yaml b/api/parameters.yaml similarity index 79% rename from oas/parameters.yaml rename to api/parameters.yaml index 659808f..f370c13 100644 --- a/oas/parameters.yaml +++ b/api/parameters.yaml @@ -4,6 +4,7 @@ Id: required: true schema: type: string + example: 5ea0450ed851c30a90e70894 Name: name: name in: path diff --git a/oas/responses.yaml b/api/responses.yaml similarity index 100% rename from oas/responses.yaml rename to api/responses.yaml diff --git a/oas/sample.yaml b/api/sample.yaml similarity index 59% rename from oas/sample.yaml rename to api/sample.yaml index b84be19..e127d74 100644 --- a/oas/sample.yaml +++ b/api/sample.yaml @@ -10,14 +10,14 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Samples' + $ref: 'api.yaml#/components/schemas/Samples' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /sample/{id}: parameters: - - $ref: 'oas.yaml#/components/parameters/Id' + - $ref: 'api.yaml#/components/parameters/Id' get: summary: TODO sample details description: 'Auth: all, levels: read, write, maintain, dev, admin' @@ -29,15 +29,15 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/SampleDetail' + $ref: 'api.yaml#/components/schemas/SampleDetail' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: TODO add/change sample description: 'Auth: basic, levels: write, maintain, dev, admin' @@ -50,24 +50,24 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Sample' + $ref: 'api.yaml#/components/schemas/Sample' responses: 200: description: samples details content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/SampleDetail' + $ref: 'api.yaml#/components/schemas/SampleDetail' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: TODO delete sample description: 'Auth: basic, levels: write, maintain, dev, admin' @@ -77,17 +77,17 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /sample/notes/fields: get: summary: TODO list all existing field names for custom notes fields @@ -107,6 +107,6 @@ type: number example: 20 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 500: - $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file + $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/oas/schemas.yaml b/api/schemas.yaml similarity index 55% rename from oas/schemas.yaml rename to api/schemas.yaml index 4d8a805..62b4690 100644 --- a/oas/schemas.yaml +++ b/api/schemas.yaml @@ -5,12 +5,13 @@ _Id: properties: _id: allOf: - - $ref: 'oas.yaml#/components/schemas/Id' + - $ref: 'api.yaml#/components/schemas/Id' readOnly: true Color: properties: color: type: string + example: black SampleProperties: properties: sample_number: @@ -24,24 +25,24 @@ SampleProperties: Samples: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' - - $ref: 'oas.yaml#/components/schemas/Color' - - $ref: 'oas.yaml#/components/schemas/SampleProperties' + - $ref: 'api.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/Color' + - $ref: 'api.yaml#/components/schemas/SampleProperties' properties: material_id: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' note_id: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' user_id: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' Sample: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' - - $ref: 'oas.yaml#/components/schemas/Color' - - $ref: 'oas.yaml#/components/schemas/SampleProperties' + - $ref: 'api.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/Color' + - $ref: 'api.yaml#/components/schemas/SampleProperties' properties: material: - $ref: 'oas.yaml#/components/schemas/Material' + $ref: 'api.yaml#/components/schemas/Material' notes: type: object properties: @@ -50,15 +51,15 @@ Sample: sample_references: type: array items: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' SampleDetail: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' - - $ref: 'oas.yaml#/components/schemas/Color' - - $ref: 'oas.yaml#/components/schemas/SampleProperties' + - $ref: 'api.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/Color' + - $ref: 'api.yaml#/components/schemas/SampleProperties' properties: material: - $ref: 'oas.yaml#/components/schemas/Material' + $ref: 'api.yaml#/components/schemas/Material' notes: type: object properties: @@ -67,63 +68,70 @@ SampleDetail: sample_references: type: array items: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' conditions: type: array items: - $ref: 'oas.yaml#/components/schemas/Condition' + $ref: 'api.yaml#/components/schemas/Condition' Material: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/_Id' properties: - material_numbers: + 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: 'oas.yaml#/components/schemas/Color' + - $ref: 'api.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 + example: 5514263423 Condition: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/_Id' properties: sample_id: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' parameters: type: object treatment_template: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' Measurement: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/_Id' properties: condition_id: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' values: type: object measurement_template: - $ref: 'oas.yaml#/components/schemas/Id' + $ref: 'api.yaml#/components/schemas/Id' Template: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/_Id' properties: name: type: string @@ -149,9 +157,9 @@ UserName: example: johndoe User: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' - - $ref: 'oas.yaml#/components/schemas/UserName' - - $ref: 'oas.yaml#/components/schemas/Email' + - $ref: 'api.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/UserName' + - $ref: 'api.yaml#/components/schemas/Email' properties: pass: type: string diff --git a/oas/template.yaml b/api/template.yaml similarity index 66% rename from oas/template.yaml rename to api/template.yaml index a09cb21..9219d81 100644 --- a/oas/template.yaml +++ b/api/template.yaml @@ -14,7 +14,7 @@ schema: type: array items: - $ref: 'oas.yaml#/components/schemas/Template' + $ref: 'api.yaml#/components/schemas/Template' example: name: heat aging parameters: @@ -22,12 +22,12 @@ range: - copper 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /templates/treatment/{name}: parameters: - - $ref: 'oas.yaml#/components/parameters/Name' + - $ref: 'api.yaml#/components/parameters/Name' get: summary: TODO treatment method details description: 'Auth: basic, levels: read, write, maintain, admin' @@ -42,7 +42,7 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/Template' + - $ref: 'api.yaml#/components/schemas/Template' example: name: heat aging parameters: @@ -50,13 +50,13 @@ range: - copper 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: TODO add/change treatment method description: 'Auth: basic, levels: maintain, admin' @@ -70,7 +70,7 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/Template' + - $ref: 'api.yaml#/components/schemas/Template' example: name: heat aging parameters: @@ -84,7 +84,7 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/Template' + - $ref: 'api.yaml#/components/schemas/Template' example: name: heat aging parameters: @@ -92,15 +92,15 @@ range: - copper 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: TODO delete treatment method description: 'Auth: basic, levels: maintain, admin' @@ -110,17 +110,17 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /template/measurements: get: summary: TODO all available measurement methods @@ -137,7 +137,7 @@ schema: type: array items: - $ref: 'oas.yaml#/components/schemas/Template' + $ref: 'api.yaml#/components/schemas/Template' example: name: humidity parameters: @@ -146,12 +146,12 @@ min: 0 max: 2 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /templates/measurement/{name}: parameters: - - $ref: 'oas.yaml#/components/parameters/Name' + - $ref: 'api.yaml#/components/parameters/Name' get: summary: TODO measurement method details description: 'Auth: basic, levels: read, write, maintain, admin' @@ -166,7 +166,7 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/Template' + - $ref: 'api.yaml#/components/schemas/Template' example: name: humidity parameters: @@ -175,13 +175,13 @@ min: 0 max: 2 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: TODO add/change measurement method description: 'Auth: basic, levels: maintain, admin' @@ -195,7 +195,7 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/Template' + - $ref: 'api.yaml#/components/schemas/Template' example: name: humidity parameters: @@ -210,7 +210,7 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/Template' + - $ref: 'api.yaml#/components/schemas/Template' example: name: humidity parameters: @@ -219,15 +219,15 @@ min: 0 max: 2 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: TODO delete measurement method description: 'Auth: basic, levels: maintain, admin' @@ -237,14 +237,14 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file + $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/oas/user.yaml b/api/user.yaml similarity index 60% rename from oas/user.yaml rename to api/user.yaml index 7d9d225..757ebf0 100644 --- a/oas/user.yaml +++ b/api/user.yaml @@ -14,13 +14,13 @@ schema: type: array items: - $ref: 'oas.yaml#/components/schemas/User' + $ref: 'api.yaml#/components/schemas/User' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /user: get: summary: list own user details @@ -35,13 +35,13 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/User' + $ref: 'api.yaml#/components/schemas/User' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: change user details description: 'Auth: basic, levels: read, write, maintain, admin' @@ -55,9 +55,9 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' - - $ref: 'oas.yaml#/components/schemas/UserName' - - $ref: 'oas.yaml#/components/schemas/Email' + - $ref: 'api.yaml#/components/schemas/_Id' + - $ref: 'api.yaml#/components/schemas/UserName' + - $ref: 'api.yaml#/components/schemas/Email' properties: pass: type: string @@ -75,15 +75,15 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/User' + $ref: 'api.yaml#/components/schemas/User' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: delete user description: 'Auth: basic, levels: read, write, maintain, admin' @@ -93,14 +93,14 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /user/{name}: parameters: - - $ref: 'oas.yaml#/components/parameters/Name' + - $ref: 'api.yaml#/components/parameters/Name' get: summary: list user details description: 'Auth: basic, levels: admin' @@ -114,15 +114,15 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/User' + $ref: 'api.yaml#/components/schemas/User' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' put: summary: change user details description: 'Auth: basic, levels: admin' @@ -135,24 +135,24 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/User' + $ref: 'api.yaml#/components/schemas/User' responses: 200: description: user details content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/User' + $ref: 'api.yaml#/components/schemas/User' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' delete: summary: delete user description: 'Auth: basic, levels: admin' @@ -162,15 +162,15 @@ - BasicAuth: [] responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /user/key: get: summary: get API key for the user @@ -190,9 +190,9 @@ type: string example: 5ea0450ed851c30a90e70899 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /user/new: post: summary: add new user @@ -214,22 +214,22 @@ - location - device_name allOf: - - $ref: 'oas.yaml#/components/schemas/User' + - $ref: 'api.yaml#/components/schemas/User' responses: 200: description: user details content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/User' + $ref: 'api.yaml#/components/schemas/User' 400: - $ref: 'oas.yaml#/components/responses/400' + $ref: 'api.yaml#/components/responses/400' 401: - $ref: 'oas.yaml#/components/responses/401' + $ref: 'api.yaml#/components/responses/401' 403: - $ref: 'oas.yaml#/components/responses/403' + $ref: 'api.yaml#/components/responses/403' 500: - $ref: 'oas.yaml#/components/responses/500' + $ref: 'api.yaml#/components/responses/500' /user/passreset: post: summary: reset password and send mail to restore @@ -244,12 +244,12 @@ application/json: schema: allOf: - - $ref: 'oas.yaml#/components/schemas/UserName' - - $ref: 'oas.yaml#/components/schemas/Email' + - $ref: 'api.yaml#/components/schemas/UserName' + - $ref: 'api.yaml#/components/schemas/Email' responses: 200: - $ref: 'oas.yaml#/components/responses/Ok' + $ref: 'api.yaml#/components/responses/Ok' 404: - $ref: 'oas.yaml#/components/responses/404' + $ref: 'api.yaml#/components/responses/404' 500: - $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file + $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/oas/material.yaml b/oas/material.yaml deleted file mode 100644 index d5d7d34..0000000 --- a/oas/material.yaml +++ /dev/null @@ -1,67 +0,0 @@ -/material/{id}: - parameters: - - $ref: 'oas.yaml#/components/parameters/Id' - get: - summary: TODO get material details - description: 'Auth: all, 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: 'Auth: basic, levels: write, maintain, dev, admin' - tags: - - /material - security: - - BasicAuth: [] - 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: 'Auth: basic, levels: write, maintain, dev, admin' - tags: - - /material - security: - - BasicAuth: [] - 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' \ No newline at end of file diff --git a/src/helpers/test.ts b/src/helpers/test.ts index 4a537aa..afd49dd 100644 --- a/src/helpers/test.ts +++ b/src/helpers/test.ts @@ -5,8 +5,9 @@ import db from "../db"; export default class TestHelper { public static auth = { - admin: {pass: 'Abc123!#', key: '5ea131671feb9c2ee0aafc9a'}, - janedoe: {pass: 'Xyz890*)', key: '5ea0450ed851c30a90e70899'} + admin: {pass: 'Abc123!#', key: '000000000000000000001003'}, + janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002'}, + user: {pass: 'Xyz890*)', key: '000000000000000000001001'} } public static res = { 400: {status: 'Bad request'}, diff --git a/src/index.ts b/src/index.ts index 67e29e1..e8cf691 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,16 +46,17 @@ app.use(require('./helpers/authorize')); // handle authentication // require routes app.use('/', require('./routes/root')); app.use('/', require('./routes/user')); +app.use('/', require('./routes/material')); // 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, {defaultModelsExpandDepth: -1, customCss: '.swagger-ui .topbar { display: none }'})); app.use((req, res) => { // 404 error handling res.status(404).json({status: 'Not found'}); diff --git a/src/models/material.ts b/src/models/material.ts new file mode 100644 index 0000000..530f8f0 --- /dev/null +++ b/src/models/material.ts @@ -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); \ No newline at end of file diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts new file mode 100644 index 0000000..bb82bc1 --- /dev/null +++ b/src/routes/material.spec.ts @@ -0,0 +1,387 @@ +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.users.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.users.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}]} + }); + }); + 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}]} + }); + }); + 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 => { + if (err) return done(err); + MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => { + if (err) return done(err); + should(data).be.eql({_id: '100000000000000000000002', name: 'UltramidTKR4355G7', 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: 'UltramidTKR4355G7'}, + 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: 400, + req: {}, + res: {status: 'Invalid id'} + }); + }); + 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).have.lengthOf(0); + 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: 400, + res: {status: 'Invalid id'} + }); + }); + 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); + console.log(res.body); + 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); + console.log(data[0]); + 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: []} + }); + }); + }); +}); \ No newline at end of file diff --git a/src/routes/material.ts b/src/routes/material.ts new file mode 100644 index 0000000..42911e3 --- /dev/null +++ b/src/routes/material.ts @@ -0,0 +1,59 @@ +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) 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) next(err); + console.log(data); + if (data) { + res.json(MaterialValidate.output(data)); + } + 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 !== undefined) { + res.status(400).json({status: 'Invalid body format'}); + return; + } + + MaterialModel.find({name: material.name}).lean().exec((err, data) => { + if(err) next(err); + if (data.length > 0) { + res.status(400).json({status: 'Material name already taken'}); + return; + } + + new MaterialModel(material).save((err, data) => { + if(err) next(err); + res.json(MaterialValidate.output(data.toObject())); + }); + }) +}) + + +module.exports = router; \ No newline at end of file diff --git a/src/routes/user.spec.ts b/src/routes/user.spec.ts index 8148d2c..b103ef7 100644 --- a/src/routes/user.spec.ts +++ b/src/routes/user.spec.ts @@ -48,6 +48,13 @@ describe('/user', () => { httpStatus: 401 }); }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/users', + httpStatus: 401 + }); + }); }); describe('GET /user/{name}', () => { @@ -119,6 +126,13 @@ describe('/user', () => { 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}', () => { @@ -169,7 +183,7 @@ describe('/user', () => { 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( 'find', (err, data) => { + 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'); @@ -193,7 +207,7 @@ describe('/user', () => { req: {level: 'read'} }).end(err => { if (err) return done (err); - UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + 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'); @@ -211,7 +225,7 @@ describe('/user', () => { }).end((err, res) => { if (err) return done (err); should(res.body).be.eql({status: 'Invalid body format'}); - UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + 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'); @@ -229,7 +243,7 @@ describe('/user', () => { }).end((err, res) => { if (err) return done (err); should(res.body).be.eql({status: 'Username already taken'}); - UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { if (err) return done(err); should(data).have.lengthOf(1); done(); @@ -312,6 +326,14 @@ describe('/user', () => { req: {} }); }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user/janedoe', + httpStatus: 401, + req: {} + }); + }); }); describe('DELETE /user/{name}', () => { @@ -324,7 +346,7 @@ describe('/user', () => { }).end((err, res) => { if (err) return done (err); should(res.body).be.eql({status: 'OK'}); - UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { if (err) return done(err); should(data).have.lengthOf(0); done(); @@ -340,7 +362,7 @@ describe('/user', () => { }).end((err, res) => { if (err) return done (err); should(res.body).be.eql({status: 'OK'}); - UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { if (err) return done(err); should(data).have.lengthOf(0); done(); @@ -379,6 +401,40 @@ describe('/user', () => { 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', () => { @@ -410,7 +466,7 @@ describe('/user', () => { 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( 'find', (err, data) => { + 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'); @@ -435,7 +491,7 @@ describe('/user', () => { }).end((err, res) => { if (err) return done (err); should(res.body).be.eql({status: 'Username already taken'}); - UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { if (err) return done(err); should(data).have.lengthOf(1); done(); @@ -510,6 +566,14 @@ describe('/user', () => { 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', () => { @@ -539,7 +603,7 @@ describe('/user', () => { }); }); it('changes the user password', done => { - UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data: any) => { + UserModel.find({name: 'janedoe'}).lean().exec( (err, data: any) => { if (err) return done(err); const oldpass = data[0].pass; TestHelper.request(server, done, { @@ -559,24 +623,4 @@ describe('/user', () => { }); }); }); - - 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 - }); - }); - }) }); \ No newline at end of file diff --git a/src/routes/user.ts b/src/routes/user.ts index f4326ea..aabb8a2 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,12 +1,14 @@ 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; @@ -168,4 +170,5 @@ router.post('/user/passreset', (req, res, next) => { }); }); + module.exports = router; \ No newline at end of file diff --git a/src/routes/validate/id.ts b/src/routes/validate/id.ts new file mode 100644 index 0000000..84024e9 --- /dev/null +++ b/src/routes/validate/id.ts @@ -0,0 +1,17 @@ +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})'; + } +} \ No newline at end of file diff --git a/src/routes/validate/material.ts b/src/routes/validate/material.ts new file mode 100644 index 0000000..c5ac005 --- /dev/null +++ b/src/routes/validate/material.ts @@ -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._id = data._id.toString(); + 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; + } +} \ No newline at end of file diff --git a/src/routes/validate/user.ts b/src/routes/validate/user.ts index 4f563c6..4b1259a 100644 --- a/src/routes/validate/user.ts +++ b/src/routes/validate/user.ts @@ -1,28 +1,34 @@ import joi from '@hapi/joi'; import globals from '../../globals'; +import IdValidate from './id'; + export default class UserValidate { // validate input for user private static user = { - _id: joi.any(), name: joi.string() .alphanum() - .lowercase(), + .lowercase() + .max(128), email: joi.string() .email({minDomainSegments: 2}) - .lowercase(), + .lowercase() + .max(128), pass: joi.string() - .pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$')), + .pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$')) + .max(128), level: joi.string() .valid(...globals.levels), location: joi.string() - .alphanum(), + .alphanum() + .max(128), device_name: joi.string() .allow('') + .max(128), }; private static specialUsernames = ['admin', 'user', 'key', 'new', 'passreset']; // names a user cannot take @@ -63,14 +69,15 @@ export default class UserValidate { // validate input for user } static output (data) { // validate output from database for needed properties, strip everything else + data._id = data._id.toString(); const {value, error} = joi.object({ - _id: joi.any(), - name: joi.string(), - email: joi.string(), - level: joi.string(), - location: joi.string(), - device_name: joi.string().allow('') - }).validate(data, {stripUnknown: true}) + _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; } diff --git a/src/test/db.json b/src/test/db.json index 4c2c645..0b4fd2f 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -2,27 +2,88 @@ "collections": { "users": [ { - "_id": {"$oid":"5ea0450ed851c30a90e70894"}, + "_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": "5ea0450ed851c30a90e70899", + "key": "000000000000000000001002", "__v": 0 }, { - "_id": {"$oid":"5ea131671feb9c2ee0aafc9b"}, + "_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": "5ea131671feb9c2ee0aafc9a", + "key": "000000000000000000001003", "__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 + } + ], + "__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 + } ] } } \ No newline at end of file diff --git a/static/styles/swagger.css b/static/styles/swagger.css new file mode 100644 index 0000000..e69de29