diff --git a/app/api/blueprint.py b/app/api/blueprint.py index 0b005ca..8b9065e 100644 --- a/app/api/blueprint.py +++ b/app/api/blueprint.py @@ -1,12 +1,10 @@ from flask_restful import Api from marshmallow import ValidationError -from app.errors import NotPresentError +from app.errors import NotPresentError, BadRequestError from flask import Blueprint, jsonify api_bp = Blueprint('api', __name__) - - api = Api(api_bp) @@ -34,6 +32,7 @@ def add_resources(): DashboardListResource, DashboardWidgetResource, DashboardWidgetListResource) + from .resources.app import MqttConfigResource, AppConfigResource api.add_resource(AccountResource, '/v1/accounts/') api.add_resource(AccountListResource, '/v1/accounts') @@ -74,6 +73,8 @@ def add_resources(): '/v1/dashboards//widgets/') api.add_resource(DashboardWidgetListResource, '/v1/dashboards//widgets') + api.add_resource(MqttConfigResource, '/v1/config/mqtt') + api.add_resource(AppConfigResource, '/v1/config') add_resources() @@ -96,6 +97,12 @@ def handle_not_present_error(e): return jsonify({'status': 'error', 'message': str(e)}), 404 +@api_bp.errorhandler(BadRequestError) +@api_bp.errorhandler(400) +def handle_bad_request_error(e): + return jsonify({'status': 'error', 'message': str(e)}), 400 + + @api_bp.errorhandler(Exception) @api_bp.errorhandler(500) def handle_unknown_errors(e): diff --git a/app/api/resources/app.py b/app/api/resources/app.py new file mode 100644 index 0000000..d83ea17 --- /dev/null +++ b/app/api/resources/app.py @@ -0,0 +1,139 @@ +from flask import current_app as app +from marshmallow import Schema, fields +from flasgger import swag_from +from app.api.auth_protection import ProtectedResource +from app.api.schemas import BaseResourceSchema + + +class BasicMqttBrokerSchema(Schema): + url = fields.String() + port = fields.String() + + +class MqttBrokerSchema(BaseResourceSchema, BasicMqttBrokerSchema): + pass + + +class BasicMqttEndpointSchema(Schema): + topic = fields.String() + description = fields.String() + body_example = fields.Raw() + + +class MqttEndpointSchema(BaseResourceSchema, BasicMqttEndpointSchema): + pass + + +class MqttConfigSchema(BaseResourceSchema): + broker = fields.Nested(BasicMqttBrokerSchema) + endpoints = fields.Nested(BasicMqttEndpointSchema, many=True) + + +class BasicVersionInfoSchema(Schema): + name = fields.String() + build_number = fields.String() + + +class VersionInfoSchema(BaseResourceSchema, BasicVersionInfoSchema): + pass + + +class BasicFrontendInfoSchema(Schema): + url = fields.String() + + +class FrontendInfoSchema(BaseResourceSchema, BasicFrontendInfoSchema): + pass + + +class BasicEmailInfoSchema(Schema): + mailer_account = fields.String() + contact_accounts = fields.List(fields.String, many=True) + + +class EmailInfoSchema(BaseResourceSchema, BasicEmailInfoSchema): + pass + + +class AppConfigSchema(BaseResourceSchema): + version = fields.Nested(BasicVersionInfoSchema) + frontend = fields.Nested(BasicFrontendInfoSchema) + email = fields.Nested(BasicEmailInfoSchema) + + +def get_mqtt_broker_info(config): + return { + 'url': config['MQTT_BROKER_URL'], + 'port': config['MQTT_BROKER_PORT'] + } + + +def get_mqtt_endpoints(config): + return [ + { + 'topic': 'device/', + 'description': 'Used by devices to send data to server. ' + + 'All messages sent to this endpoint must be in ' + + 'JSON format and signed with device secret (sign' + + 'ature should be added to original JSON after ' + + 'signing as "hmac" key in root object). JSON can ' + + 'contain any number of keys, but there are a few ' + + 'mandatory fields.', + 'body_example': { + 'record_type': 1, + 'record_value': 123, + 'recorded_at': 1537379424, + 'hmac': "jfdhslfh12383j12l3j12oirjfkdsfd" + } + }, + { + 'topic': 'device//config', + 'description': 'Used by server to send config to ' + + 'devices. Devices should listen to this endpoint ' + + 'in order to properly receive updated ' + + 'configuration.', + 'body_example': { + 'update_rate': 4, + 'mode': 'passive' + } + } + ] + + +def get_app_version_info(config): + return { + 'name': config['APP_VERSION'], + 'build_number': config['APP_RELEASE_VERSION_STRING'] + } + + +def get_frontend_info(config): + return { + 'url': config['FRONTEND_URL'] + } + + +def get_email_info(config): + return { + 'mailer_account': config['MAIL_DEFAULT_SENDER'], + 'contact_accounts': config['MAIL_CONTACT_ACCOUNTS'] + } + + +class MqttConfigResource(ProtectedResource): + @swag_from('swagger/get_mqtt_config_spec.yaml') + def get(self): + return MqttConfigSchema().dump({ + 'broker': get_mqtt_broker_info(app.config), + 'endpoints': get_mqtt_endpoints(app.config) + }), 200 + + +class AppConfigResource(ProtectedResource): + @swag_from('swagger/get_app_config_spec.yaml') + def get(self): + return AppConfigSchema().dump({ + 'version': get_app_version_info(app.config), + 'frontend': get_frontend_info(app.config), + 'email': get_email_info(app.config) + }), 200 diff --git a/app/api/resources/swagger/get_app_config_spec.yaml b/app/api/resources/swagger/get_app_config_spec.yaml new file mode 100644 index 0000000..beb47d9 --- /dev/null +++ b/app/api/resources/swagger/get_app_config_spec.yaml @@ -0,0 +1,14 @@ +Gets server app configuration and description +--- +tags: + - Config +responses: + 200: + description: Success + schema: + type: object + required: + - content + properties: + content: + $ref: '#/definitions/AppConfig' diff --git a/app/api/resources/swagger/get_mqtt_config_spec.yaml b/app/api/resources/swagger/get_mqtt_config_spec.yaml new file mode 100644 index 0000000..195a6a8 --- /dev/null +++ b/app/api/resources/swagger/get_mqtt_config_spec.yaml @@ -0,0 +1,15 @@ +Gets server Mqtt configuration and description +--- +tags: + - Mqtt + - Config +responses: + 200: + description: Success + schema: + type: object + required: + - content + properties: + content: + $ref: '#/definitions/MqttConfig' diff --git a/app/errors.py b/app/errors.py index df97152..af3f572 100644 --- a/app/errors.py +++ b/app/errors.py @@ -1,5 +1,6 @@ class NotPresentError(Exception): pass + class BadRequestError(Exception): pass diff --git a/app/swagger/template.yaml b/app/swagger/template.yaml index 5d98951..5371f1b 100644 --- a/app/swagger/template.yaml +++ b/app/swagger/template.yaml @@ -328,6 +328,109 @@ definitions: name: ref: '#definitions/genericname' + VersionInfo: + type: object + required: + - name + - build_number + properties: + name: + type: string + description: Version name following semantic versioning + example: 0.4.3 + build_number: + type: string + description: Number of current build/release + example: v72 + + FrontendInfo: + type: object + required: + - url + properties: + url: + type: string + description: URL of frontend used with this backend. + example: https://iot-frontend-app.herokuapp.com + + EmailConfigInfo: + type: object + required: + - mailer_account + - contact_accounts + properties: + mailer_account: + type: string + description: Account used to send emails to users + example: final.iot.backend.mailer@gmail.com + contact_accounts: + type: string + description: Emails used to contact developers + example: [] + + AppConfig: + type: object + required: + - version + - frontend + - email + properties: + version: + $ref: '#/definitions/VersionInfo' + frontend: + $ref: '#/definitions/FrontendInfo' + email: + $ref: '#/definitions/EmailConfigInfo' + + + MqttBroker: + type: object + required: + - url + - port + properties: + url: + type: string + description: Url of the used MQTT broker + example: broker.hivemq.com + port: + type: number + description: Port of the used MQTT broker + example: 1883 + + MqttEndpoint: + type: object + required: + - topic + - description + - body_example + properties: + topic: + type: string + description: Topic of this endpoint + example: device/ + description: + type: string + description: Description of usage of this endpoint + example: Used to send data to devices + body_example: + type: object + description: Example of body of messages used on this endpoint + example: {} + + MqttConfig: + type: object + required: + - broker + - endpoints + properties: + broker: + $ref: '#/definitions/MqttBroker' + endpoints: + type: array + items: + $ref: '#/definitions/MqttEndpoint' + Widget: type: object required: diff --git a/config.py b/config.py index 83a8cb9..c45a067 100644 --- a/config.py +++ b/config.py @@ -2,7 +2,9 @@ import os # App configuration DEBUG = os.environ['DEBUG'] -APP_VERSION = '0.4.2' +APP_VERSION = '0.4.3' +APP_RELEASE_VERSION_STRING = (os.environ.get('HEROKU_RELEASE_VERSION') + or 'Unknown') # Define the application directory BASE_DIR = os.path.abspath(os.path.dirname(__file__)) @@ -56,6 +58,7 @@ MAIL_PASSWORD = os.environ['APP_MAIL_PASSWORD'] # mail accounts MAIL_DEFAULT_SENDER = 'final.iot.backend.mailer@gmail.com' +MAIL_CONTACT_ACCOUNTS = ['esarajcic1@etf.unsa.ba', 'valjic1@etf.unsa.ba'] # frontend FRONTEND_URL = (os.environ.get('IOT_FRONTEND_URL') or