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 flask_restful import Api
from marshmallow import ValidationError from marshmallow import ValidationError
from app.errors import NotPresentError from app.errors import NotPresentError, BadRequestError
from flask import Blueprint, jsonify from flask import Blueprint, jsonify
api_bp = Blueprint('api', __name__) api_bp = Blueprint('api', __name__)
api = Api(api_bp) api = Api(api_bp)
@ -34,6 +32,7 @@ def add_resources():
DashboardListResource, DashboardListResource,
DashboardWidgetResource, DashboardWidgetResource,
DashboardWidgetListResource) DashboardWidgetListResource)
from .resources.app import MqttConfigResource, AppConfigResource
api.add_resource(AccountResource, '/v1/accounts/<int:account_id>') api.add_resource(AccountResource, '/v1/accounts/<int:account_id>')
api.add_resource(AccountListResource, '/v1/accounts') api.add_resource(AccountListResource, '/v1/accounts')
@ -74,6 +73,8 @@ def add_resources():
'/v1/dashboards/<int:dashboard_id>/widgets/<int:widget_id>') '/v1/dashboards/<int:dashboard_id>/widgets/<int:widget_id>')
api.add_resource(DashboardWidgetListResource, api.add_resource(DashboardWidgetListResource,
'/v1/dashboards/<int:dashboard_id>/widgets') '/v1/dashboards/<int:dashboard_id>/widgets')
api.add_resource(MqttConfigResource, '/v1/config/mqtt')
api.add_resource(AppConfigResource, '/v1/config')
add_resources() add_resources()
@ -96,6 +97,12 @@ def handle_not_present_error(e):
return jsonify({'status': 'error', 'message': str(e)}), 404 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(Exception)
@api_bp.errorhandler(500) @api_bp.errorhandler(500)
def handle_unknown_errors(e): 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): class NotPresentError(Exception):
pass pass
class BadRequestError(Exception): class BadRequestError(Exception):
pass pass

View File

@ -328,6 +328,109 @@ definitions:
name: name:
ref: '#definitions/genericname' 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: Widget:
type: object type: object
required: required:

View File

@ -2,7 +2,9 @@ import os
# App configuration # App configuration
DEBUG = os.environ['DEBUG'] 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 # Define the application directory
BASE_DIR = os.path.abspath(os.path.dirname(__file__)) BASE_DIR = os.path.abspath(os.path.dirname(__file__))
@ -56,6 +58,7 @@ MAIL_PASSWORD = os.environ['APP_MAIL_PASSWORD']
# mail accounts # mail accounts
MAIL_DEFAULT_SENDER = 'final.iot.backend.mailer@gmail.com' MAIL_DEFAULT_SENDER = 'final.iot.backend.mailer@gmail.com'
MAIL_CONTACT_ACCOUNTS = ['esarajcic1@etf.unsa.ba', 'valjic1@etf.unsa.ba']
# frontend # frontend
FRONTEND_URL = (os.environ.get('IOT_FRONTEND_URL') or FRONTEND_URL = (os.environ.get('IOT_FRONTEND_URL') or