commit
c116d30ec9
|
@ -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):
|
||||||
|
|
|
@ -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
|
|
@ -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'
|
|
@ -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'
|
|
@ -1,5 +1,6 @@
|
||||||
class NotPresentError(Exception):
|
class NotPresentError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BadRequestError(Exception):
|
class BadRequestError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue