diff --git a/Pipfile b/Pipfile index 34354e9930a8d2a83c8e2183194356767e85a955..af51971f70bc61506574281a3faf774ecac34541 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 0000000000000000000000000000000000000000..5ce5201b64435ebad3524a6c8463092d9a221eb9 --- /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 6e8dbe2a869bfbad7024f04eb15d482a9dde88e8..5df98352227144edce59bc82a316be934276cfae 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 da2271fcef77c9dcde397cd8fa4869aca6dd2cd7..b0e4273d1c725944161ec46b7f84cc59300742ab 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 0000000000000000000000000000000000000000..86f22315bde97ce4bdf782e69e8ad85b496707a5 --- /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>