Merged in develop (pull request #58)

Version 0.4.3 release
master
Ensar Sarajcic 2018-11-04 12:48:37 +00:00
commit c116d30ec9
7 changed files with 286 additions and 4 deletions

View File

@ -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/<int:account_id>')
api.add_resource(AccountListResource, '/v1/accounts')
@ -74,6 +73,8 @@ def add_resources():
'/v1/dashboards/<int:dashboard_id>/widgets/<int:widget_id>')
api.add_resource(DashboardWidgetListResource,
'/v1/dashboards/<int:dashboard_id>/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):

View File

@ -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/<device_id>',
'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/<device_id>/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

View File

@ -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'

View File

@ -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'

View File

@ -1,5 +1,6 @@
class NotPresentError(Exception):
pass
class BadRequestError(Exception):
pass

View File

@ -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/<device_id>
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:

View File

@ -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