From 387a55eebc27d9993e36b7835b38da4811f32650 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Wed, 23 Sep 2020 18:55:37 +0200
Subject: [PATCH] serve open api swagger docs

---
 Pipfile                  |    1 +
 docs/api.yml             | 1060 ++++++++++++++++++++++++++++++++++++++
 src/StorageLeaf.py       |    2 +-
 src/blueprints/Routes.py |   20 +-
 src/templates/api.html   |   53 ++
 5 files changed, 1132 insertions(+), 4 deletions(-)
 create mode 100644 docs/api.yml
 create mode 100644 src/templates/api.html

diff --git a/Pipfile b/Pipfile
index 34354e9..af51971 100644
--- a/Pipfile
+++ b/Pipfile
@@ -16,5 +16,6 @@ flask = "==1.1.2"
 gevent = "==20.9.0"
 TheCodeLabs-BaseUtils = "*"
 TheCodeLabs-FlaskUtils = "*"
+pyyaml = "==5.3.1"
 
 [dev-packages]
diff --git a/docs/api.yml b/docs/api.yml
new file mode 100644
index 0000000..5ce5201
--- /dev/null
+++ b/docs/api.yml
@@ -0,0 +1,1060 @@
+openapi: 3.0.0
+servers:
+  - description: StorageLeaf API
+    url: https://localhost/
+info:
+  description: The StorageLeaf API
+  version: "2.7.0"
+  title: StorageLeaf API
+tags:
+  - name: admins
+    description: Secured Admin-only calls
+  - name: public
+    description: Operations available to the public
+
+paths:
+  /version:
+    get:
+      tags:
+        - public
+      summary: Gets information about the server version
+      operationId: version
+      responses:
+        '200':
+          description: The server version information
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Version'
+  /login:
+    post:
+      tags:
+        - public
+      summary: Gets a bearer JSON Web Token
+      operationId: login
+      requestBody:
+        description: Credentials
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Credentials'
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/TokenResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+
+  /roadmaps:
+    get:
+      tags:
+        - public
+      summary: Gets all roadmaps
+      operationId: roadmaps
+      responses:
+        '200':
+          description: All available roadmaps
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Roadmap'
+  /roadmap/{roadmapID}:
+    get:
+      tags:
+        - public
+      summary: Gets  a specific roadmap
+      operationId: roadmap
+      parameters:
+        - in: path
+          name: roadmapID
+          description: The roadmap ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: The roadmap
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Roadmap'
+    delete:
+      tags:
+        - admins
+      description: Deletes a roadmap based on the given ID
+      operationId: deleteRoadmap
+      parameters:
+        - in: path
+          name: roadmapID
+          description: The roadmap ID
+          required: true
+          schema:
+            type: integer
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /roadmap:
+    post:
+      tags:
+        - admins
+      summary: Adds a new roadmap
+      operationId: addRoadmap
+      requestBody:
+        description: Roadmap to add
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/NewRoadmap'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    put:
+      tags:
+        - admins
+      summary: Updates a roadmap
+      operationId: updateRoadmap
+      requestBody:
+        description: Roadmap to update
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Roadmap'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /roadmap/{roadmapID}/full:
+    get:
+      tags:
+        - public
+      summary: Gets  a specific roadmap with all milestones, tasks and sub tasks
+      operationId: roadmapFull
+      parameters:
+        - in: path
+          name: roadmapID
+          description: The roadmap ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: The roadmap
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/RoadmapFull'
+
+  /milestones/{roadmapID}:
+    get:
+      tags:
+        - public
+      summary: Gets all milestones for a roadmap
+      operationId: milestones
+      parameters:
+        - in: path
+          name: roadmapID
+          description: The roadmap ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: All milestones for the given roadmap ID
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Milestone'
+  /milestones/{roadmapID}/open:
+    get:
+      tags:
+        - public
+      summary: Gets all open milestones for a roadmap
+      operationId: openMilestones
+      parameters:
+        - in: path
+          name: roadmapID
+          description: The roadmap ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: All open milestones for the given roadmap ID
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Milestone'
+  /milestones/{roadmapID}/latest:
+    get:
+      tags:
+        - public
+      summary: Gets the lastest finished milestone for a roadmap
+      operationId: latestMilestone
+      parameters:
+        - in: path
+          name: roadmapID
+          description: The roadmap ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: The latest finished milestone for the given roadmap ID
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Milestone'
+  /milestone/{milestoneID}:
+    get:
+      tags:
+        - public
+      summary: Gets  a specific milestone
+      operationId: milestone
+      parameters:
+        - in: path
+          name: milestoneID
+          description: The milestone ID
+          required: true
+          schema:
+            type: string
+      responses:
+        '200':
+          description: The milestone
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Milestone'
+    delete:
+      tags:
+        - admins
+      description: Deletes a milestone based on the given ID
+      operationId: deleteMilestone
+      parameters:
+        - in: path
+          name: milestoneID
+          description: The milestone ID
+          required: true
+          schema:
+            type: integer
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /milestone:
+    post:
+      tags:
+        - admins
+      summary: Adds a new milestone
+      operationId: addMilestone
+      requestBody:
+        description: Milestone to add
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/NewMilestone'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    put:
+      tags:
+        - admins
+      summary: Updates a milestone
+      operationId: updateMilestone
+      requestBody:
+        description: Milestone to update
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Milestone'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+
+  /milestone/{milestoneID}/close:
+    post:
+      tags:
+        - admins
+      summary: Closes a milestone and all corresponding tasks and sub tasks
+      operationId: closeMilestone
+      parameters:
+        - in: path
+          name: milestoneID
+          description: The milestone ID
+          required: true
+          schema:
+            type: integer
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+
+  /tasks/{milestoneID}:
+    get:
+      tags:
+        - public
+      summary: Gets all tasks for a milestone
+      operationId: tasks
+      parameters:
+        - in: path
+          name: milestoneID
+          description: The milestone ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: All tasks for the given milestone ID
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Task'
+  /tasks/{milestoneID}/open:
+    get:
+      tags:
+        - public
+      summary: Gets all open tasks for a milestone
+      operationId: openTasks
+      parameters:
+        - in: path
+          name: milestoneID
+          description: The milestone ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: All open tasks for the given milestone ID
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Task'
+  /task/{taskID}:
+    get:
+      tags:
+        - public
+      summary: Gets  a specific task
+      operationId: task
+      parameters:
+        - in: path
+          name: taskID
+          description: The task ID
+          required: true
+          schema:
+            type: string
+      responses:
+        '200':
+          description: The task
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Task'
+    delete:
+      tags:
+        - admins
+      description: Deletes a task based on the given ID
+      operationId: deleteTask
+      parameters:
+        - in: path
+          name: taskID
+          description: The task ID
+          required: true
+          schema:
+            type: integer
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /task:
+    post:
+      tags:
+        - admins
+      summary: Adds a new task
+      operationId: addTask
+      requestBody:
+        description: Task to add
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/NewTask'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    put:
+      tags:
+        - admins
+      summary: Updates a task
+      operationId: updateTask
+      requestBody:
+        description: Task to update
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Task'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+
+  /subtasks/{taskID}:
+    get:
+      tags:
+        - public
+      summary: Gets all sub taks for a task
+      operationId: substasks
+      parameters:
+        - in: path
+          name: taskID
+          description: The task ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: All sub tasks for the given task ID
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/SubTask'
+  /subtasks/{taskID}/open:
+    get:
+      tags:
+        - public
+      summary: Gets all open sub taks for a task
+      operationId: openSubtasks
+      parameters:
+        - in: path
+          name: taskID
+          description: The task ID
+          required: true
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: All open sub tasks for the given task ID
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/SubTask'
+  /subtask/{subtaskID}:
+    get:
+      tags:
+        - public
+      summary: Gets  a specific sub task
+      operationId: subtask
+      parameters:
+        - in: path
+          name: subtaskID
+          description: The sub task ID
+          required: true
+          schema:
+            type: string
+      responses:
+        '200':
+          description: The sub task
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SubTask'
+    delete:
+      tags:
+        - admins
+      description: Deletes a sub task based on the given ID
+      operationId: deleteSubTask
+      parameters:
+        - in: path
+          name: subtaskID
+          description: The sub task ID
+          required: true
+          schema:
+            type: integer
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /subtask:
+    post:
+      tags:
+        - admins
+      summary: Adds a new sub task
+      operationId: addSubTask
+      requestBody:
+        description: Sub task to add
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/NewSubTask'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    put:
+      tags:
+        - admins
+      summary: Updates a sub task
+      operationId: updateSubTask
+      requestBody:
+        description: Sub task to update
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/SubTask'
+      security:
+        - BearerAuth: []
+      responses:
+        '200':
+          description: success response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SuccessResponse'
+        '401':
+          description: unauthorized response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UnauthorizedResponse'
+        default:
+          description: error response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+
+components:
+  securitySchemes:
+    BearerAuth:
+      type: http
+      scheme: bearer
+      bearerFormat: JWT
+
+  schemas:
+    Version:
+      type: object
+      required:
+        - code
+        - name
+        - date
+      properties:
+        code:
+          type: integer
+          example: 1
+        name:
+          type: string
+          example: "v1.0.0"
+        date:
+          type: string
+          format: date
+          example: "30.05.19"
+
+    Credentials:
+      type: object
+      required:
+        - username
+        - password
+      properties:
+        username:
+          type: string
+          example: "admin"
+        password:
+          type: string
+          example: "123"
+
+    Roadmap:
+      type: object
+      required:
+        - ID
+        - Projectname
+      properties:
+        ID:
+          type: integer
+          example: 1
+        Projectname:
+          type: string
+          example: "Example Roadmap"
+
+    RoadmapFull:
+      allOf:
+        - $ref: '#/components/schemas/Roadmap'
+        - type: object
+          properties:
+            milestones:
+              type: array
+              items:
+                allOf:
+                  - $ref: '#/components/schemas/Milestone'
+                  - type: object
+                    properties:
+                      tasks:
+                        type: array
+                        items:
+                          allOf:
+                            - $ref: '#/components/schemas/Task'
+                            - type: object
+                              properties:
+                                subtasks:
+                                  type: array
+                                  items:
+                                    $ref: '#/components/schemas/SubTask'
+
+    NewRoadmap:
+      type: object
+      required:
+        - ID
+        - Projectname
+      properties:
+        ID:
+          type: integer
+          example: 1
+        Projectname:
+          type: string
+          example: "Example Roadmap"
+
+    Milestone:
+      type: object
+      required:
+        - ID
+        - RoadmapID
+        - VersionCode
+        - VersionName
+        - Title
+        - DueDate
+        - CompletionDate
+        - Status
+      properties:
+        ID:
+          type: integer
+          example: 1
+        RoadmapID:
+          type: integer
+          example: 2
+        VersionCode:
+          type: integer
+          example: 3
+        VersionName:
+          type: string
+          example: "v1.0.0"
+        Title:
+          type: string
+          example: "My awesome milestone"
+        DueDate:
+          type: string
+          format: date
+          example: "2019-05-30"
+        CompletionDate:
+          type: string
+          format: date
+          example: "2019-05-30"
+        Status:
+          type: integer
+          enum:
+            - 0
+            - 1
+
+    NewMilestone:
+      type: object
+      required:
+        - RoadmapID
+        - VersionCode
+        - VersionName
+        - Title
+        - DueDate
+        - CompletionDate
+        - Status
+      properties:
+        RoadmapID:
+          type: integer
+          example: 2
+        VersionCode:
+          type: integer
+          example: 3
+        VersionName:
+          type: string
+          example: "v1.0.0"
+        Title:
+          type: string
+          example: "My awesome milestone"
+        DueDate:
+          type: string
+          format: date
+          example: "2019-05-30"
+        CompletionDate:
+          type: string
+          format: date
+          example: "2019-05-30"
+        Status:
+          type: integer
+          enum:
+            - 0
+            - 1
+
+    Task:
+      type: object
+      required:
+        - ID
+        - MilestoneID
+        - Title
+        - Description
+        - Status
+      properties:
+        ID:
+          type: integer
+          example: 1
+        MilestoneID:
+          type: integer
+          example: 2
+        Title:
+          type: string
+          example: "My awesome task"
+        Description:
+          type: string
+          example: "Lorem Ipsum dolor sit amet"
+        Status:
+          type: integer
+          enum:
+            - 0
+            - 1
+
+    NewTask:
+      type: object
+      required:
+        - MilestoneID
+        - Title
+        - Description
+        - Status
+      properties:
+        MilestoneID:
+          type: integer
+          example: 2
+        Title:
+          type: string
+          example: "My awesome task"
+        Description:
+          type: string
+          example: "Lorem Ipsum dolor sit amet"
+        Status:
+          type: integer
+          enum:
+            - 0
+            - 1
+
+    SubTask:
+      type: object
+      required:
+        - ID
+        - TaskID
+        - Title
+        - Description
+        - Status
+      properties:
+        ID:
+          type: integer
+          example: 1
+        TaskID:
+          type: integer
+          example: 2
+        Title:
+          type: string
+          example: "My awesome sub task"
+        Description:
+          type: string
+          example: "Lorem Ipsum dolor sit amet"
+        Status:
+          type: integer
+          enum:
+            - 0
+            - 1
+
+    NewSubTask:
+      type: object
+      required:
+        - TaskID
+        - Title
+        - Description
+        - Status
+      properties:
+        TaskID:
+          type: integer
+          example: 2
+        Title:
+          type: string
+          example: "My awesome sub task"
+        Description:
+          type: string
+          example: "Lorem Ipsum dolor sit amet"
+        Status:
+          type: integer
+          enum:
+            - 0
+            - 1
+
+    UnauthorizedResponse:
+      description: Access token is missing or invalid
+      properties:
+        msg:
+          type: string
+          example: "Missing Authorization Header"
+    TokenResponse:
+      description: JSON web token
+      properties:
+        access_token:
+          type: string
+    SuccessResponse:
+      required:
+        - success
+      properties:
+        success:
+          type: boolean
+    ErrorResponse:
+      required:
+        - success
+        - msg
+      properties:
+        success:
+          type: boolean
+        msg:
+          type: string
\ No newline at end of file
diff --git a/src/StorageLeaf.py b/src/StorageLeaf.py
index 6e8dbe2..5df9835 100644
--- a/src/StorageLeaf.py
+++ b/src/StorageLeaf.py
@@ -14,7 +14,7 @@ class StorageLeaf(FlaskBaseApp):
         super().__init__(appName, os.path.dirname(__file__), LOGGER, serveRobotsTxt=False)
 
     def _register_blueprints(self, app):
-        app.register_blueprint(Routes.construct_blueprint(self._settings))
+        app.register_blueprint(Routes.construct_blueprint(self._settings, self._version))
         return app
 
 
diff --git a/src/blueprints/Routes.py b/src/blueprints/Routes.py
index da2271f..b0e4273 100644
--- a/src/blueprints/Routes.py
+++ b/src/blueprints/Routes.py
@@ -1,7 +1,11 @@
+import json
+import os
 from enum import Enum
 
-from flask import Blueprint, request, jsonify
+import yaml
+from flask import Blueprint, request, jsonify, send_from_directory, render_template
 
+from logic import Constants
 from logic.Database import Database
 from logic.RequestValidator import ValidationError, RequestValidator
 
@@ -24,12 +28,22 @@ class SensorParameters(Enum):
         return [m.value for m in SensorParameters]
 
 
-def construct_blueprint(settings):
+def construct_blueprint(settings, version):
     routes = Blueprint('routes', __name__)
 
     @routes.route('/', methods=['GET'])
     def index():
-        return "Hello World!"
+        yamlPath = os.path.join(Constants.ROOT_DIR, 'docs', 'api.yml')
+        with open(yamlPath, 'r') as yamlFile:
+            specification = yaml.load(yamlFile, Loader=yaml.FullLoader)
+
+        specification['servers'][0]['url'] = '0815'
+        specification['info']['version'] = version['name']
+
+        specification = json.dumps(specification)
+        return render_template('api.html',
+                               appName=Constants.APP_NAME,
+                               openApiSpecification=specification)
 
     @routes.route('/device/<deviceName>', methods=['POST'])
     def postSensorData(deviceName):
diff --git a/src/templates/api.html b/src/templates/api.html
new file mode 100644
index 0000000..86f2231
--- /dev/null
+++ b/src/templates/api.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+    <html lang="en">
+    <head>
+      <meta charset="UTF-8">
+      <title>{{ appName }} API</title>
+      <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
+      <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.22.2/swagger-ui.css" >
+      <style>
+        html
+        {
+          box-sizing: border-box;
+          overflow: -moz-scrollbars-vertical;
+          overflow-y: scroll;
+        }
+        *,
+        *:before,
+        *:after
+        {
+          box-sizing: inherit;
+        }
+
+        body {
+          margin:0;
+          background: #fafafa;
+        }
+      </style>
+    </head>
+    <body>
+
+        <div id="swagger-ui"></div>
+
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.22.2/swagger-ui-bundle.js"> </script>
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.22.2/swagger-ui-standalone-preset.js"> </script>
+        <script>
+        window.onload = function() {
+
+          var spec = {{ openApiSpecification|safe }};
+
+          // Build a system
+          const ui = SwaggerUIBundle({
+            spec: spec,
+            dom_id: '#swagger-ui',
+            deepLinking: true,
+            presets: [
+              SwaggerUIBundle.presets.apis
+            ]
+          })
+
+          window.ui = ui
+        }
+        </script>
+    </body>
+</html>
-- 
GitLab