Merged in develop (pull request #5)

Version 0.2.0 release
master
Ensar Sarajcic 2018-10-08 20:09:57 +00:00
commit 9da4d3eea9
23 changed files with 83 additions and 153 deletions

View File

@ -7,9 +7,10 @@ import app.accounts.api as accounts
from app.api.auth_protection import ProtectedResource
from app.api.permission_protection import (requires_permission,
valid_permissions)
from app.api.schemas import BaseResourceSchema
class UserSchema(Schema):
class UserSchema(BaseResourceSchema):
username = fields.Str(required=True)
email = fields.Email(required=True)
password = fields.Str(required=True, load_only=True)
@ -23,7 +24,7 @@ def validate_role_permissions(permissions_list):
return set(permissions_list).issubset(valid_permissions)
class RoleSchema(Schema):
class RoleSchema(BaseResourceSchema):
id = fields.Integer(required=True, location='json')
display_name = fields.String(required=True, location='json')
permissions = fields.List(fields.String, required=True,
@ -31,50 +32,32 @@ class RoleSchema(Schema):
validate=validate_role_permissions)
class RoleWrapperSchema(Schema):
role = fields.Nested(RoleSchema, required=True, location='json')
class RolesWrapperSchema(Schema):
roles = fields.Nested(RoleSchema, required=True,
location='json', many=True)
class RoleCreationSchema(Schema):
display_name = fields.String(required=True, location='json')
permissions = fields.List(fields.String, required=True,
location='json', many=True)
class RoleCreationWrapperSchema(Schema):
role = fields.Nested(RoleCreationSchema, required=True, location='json')
class UserWrapperSchema(Schema):
user = fields.Nested(UserSchema, required=True, location='json')
class AccountResource(ProtectedResource):
@swag_from('swagger/get_account_spec.yaml')
def get(self, account_id):
if g.current_account.id == account_id:
return UserWrapperSchema().dump({'user': g.current_account}), 200
return UserSchema().dump(g.current_account), 200
abort(403, message='You can only get your own account', status='error')
class RoleResource(ProtectedResource):
@swag_from('swagger/get_role_spec.yaml')
def get(self, role_id):
return RoleWrapperSchema().dump(
{'role': accounts.get_role(role_id)}), 200
return RoleSchema().dump(
accounts.get_role(role_id)), 200
class RolesResource(ProtectedResource):
@requires_permission('CREATE_ROLE', 'Role creation')
@use_args(RoleCreationWrapperSchema())
@use_args(RoleCreationSchema(), locations=('json',))
@swag_from('swagger/create_role_spec.yaml')
def post(self, args):
args = args['role']
success = accounts.create_role(args['display_name'],
args['permissions'])
if success:
@ -82,12 +65,11 @@ class RolesResource(ProtectedResource):
@swag_from('swagger/get_roles_spec.yaml')
def get(self):
return RolesWrapperSchema().dump(
{'roles': accounts.get_all_roles()}), 200
return RoleSchema().dump(accounts.get_all_roles(), many=True), 200
class AccountRoleResource(ProtectedResource):
@use_args(RoleUpdateSchema())
@use_args(RoleUpdateSchema(), locations=('json',))
@swag_from('swagger/update_account_role_spec.yaml')
def put(self, args, account_id):
if g.current_account.id == account_id:
@ -99,11 +81,10 @@ class AccountRoleResource(ProtectedResource):
class AccountListResource(Resource):
@use_args(UserWrapperSchema())
@use_args(UserSchema(), locations=('json',))
@swag_from('swagger/create_account_spec.yaml')
def post(self, args):
try:
args = args['user']
success = accounts.create_account(
args['username'],
args['email'],

View File

@ -1,26 +1,18 @@
from flask import g
from flask_restful import abort
from marshmallow import Schema, fields
from marshmallow import fields
from webargs.flaskparser import use_args
from flasgger import swag_from
import app.dashboards.api as dashboard
from app.api.auth_protection import ProtectedResource
from app.api.schemas import BaseResourceSchema
class DashboardSchema(Schema):
class DashboardSchema(BaseResourceSchema):
id = fields.Integer(dump_only=True)
dashboard_data = fields.Raw()
class DashboardWrapperSchema(Schema):
dashboard = fields.Nested(DashboardSchema, required=True, location='json')
class DashboardsSchema(Schema):
dashboards = fields.Nested(DashboardSchema, required=True, location='json',
many=True)
class DashboardResource(ProtectedResource):
@swag_from('swagger/get_dashboard_spec.yaml')
def get(self, dashboard_id):
@ -28,17 +20,15 @@ class DashboardResource(ProtectedResource):
if requested_dashboard.account_id != g.current_account.id:
abort(403, message='You are not allowed to access this dashboard',
status='error')
return DashboardWrapperSchema().dump(
{'dashboard': requested_dashboard}), 200
return DashboardSchema().dump(requested_dashboard), 200
@use_args(DashboardWrapperSchema())
@use_args(DashboardSchema(), locations=('json',))
@swag_from('swagger/update_dashboard_spec.yaml')
def put(self, args, dashboard_id):
requested_dashboard = dashboard.get_dashboard(dashboard_id)
if requested_dashboard.account_id != g.current_account.id:
abort(403, message='You are not allowed to access this dashboard',
status='error')
args = args['dashboard']
success = dashboard.update_dashboard(
dashboard_id,
args['dashboard_data'])
@ -47,10 +37,9 @@ class DashboardResource(ProtectedResource):
class DashboardListResource(ProtectedResource):
@use_args(DashboardWrapperSchema())
@use_args(DashboardSchema(), locations=('json',))
@swag_from('swagger/create_dashboard_spec.yaml')
def post(self, args):
args = args['dashboard']
success = dashboard.create_dashboard(
args['dashboard_data'],
g.current_account.id)
@ -59,6 +48,5 @@ class DashboardListResource(ProtectedResource):
@swag_from('swagger/get_dashboards_spec.yaml')
def get(self):
return DashboardsSchema().dump(
{'dashboards':
dashboard.get_dashboards(g.current_account.id)}), 200
return DashboardSchema().dump(
dashboard.get_dashboards(g.current_account.id), many=True), 200

View File

@ -5,17 +5,22 @@ from flasgger import swag_from
from flask import g, request
import app.devices.api as devices
from app.api.auth_protection import ProtectedResource
from app.api.schemas import BaseResourceSchema
class DeviceTypeSchema(Schema):
class BasicDeviceTypeSchema(Schema):
id = fields.Integer(dump_only=True)
name = fields.Str(required=True)
class DeviceSchema(Schema):
class DeviceTypeSchema(BaseResourceSchema, BasicDeviceTypeSchema):
pass
class DeviceSchema(BaseResourceSchema):
id = fields.Integer(dump_only=True)
name = fields.Str(required=True)
device_type = fields.Nested(DeviceTypeSchema, dump_only=True)
device_type = fields.Nested(BasicDeviceTypeSchema, dump_only=True)
device_type_id = fields.Integer(load_only=True, missing=1)
@ -23,37 +28,12 @@ class DeviceWithConfigurationSchema(DeviceSchema):
configuration = fields.Raw(dump_only=True)
class DeviceWrapperSchema(Schema):
device = fields.Nested(DeviceWithConfigurationSchema,
required=True, location='json')
class DevicesWrapperSchema(Schema):
devices = fields.Nested(DeviceSchema, required=True,
location='json', many=True)
class DeviceTypeWrapperSchema(Schema):
device_type = fields.Nested(DeviceTypeSchema, required=True,
location='json')
class DeviceTypesWrapperSchema(Schema):
device_types = fields.Nested(DeviceTypeSchema, required=True,
location='json', many=True)
class RecordingsSchema(Schema):
class RecordingsSchema(BaseResourceSchema):
recorded_at = fields.DateTime()
record_type = fields.Integer()
record_value = fields.String()
class RecordingsWrapperSchema(Schema):
recordings = fields.Nested(RecordingsSchema, required=True,
location='json', many=True)
def validate_device_ownership(device_id):
if not devices.can_user_access_device(g.current_account.id, device_id):
abort(403, message='You are not allowed to access this device',
@ -64,8 +44,8 @@ class DeviceResource(ProtectedResource):
@swag_from('swagger/get_device_spec.yaml')
def get(self, device_id):
validate_device_ownership(device_id)
return DeviceWrapperSchema().dump(
{'device': devices.get_device(device_id)}), 200
return DeviceSchema().dump(
devices.get_device(device_id)), 200
@swag_from('swagger/delete_device_spec.yaml')
def delete(self, device_id):
@ -77,18 +57,17 @@ class DeviceResource(ProtectedResource):
class DeviceTypeResource(ProtectedResource):
@swag_from('swagger/get_device_type_spec.yaml')
def get(self, device_type_id):
return DeviceTypeWrapperSchema().dump(
{'device_type': devices.get_device_type(device_type_id)}), 200
return DeviceTypeSchema().dump(
devices.get_device_type(device_type_id)), 200
class DeviceTypeListResource(ProtectedResource):
@use_args(DeviceTypeWrapperSchema())
@use_args(DeviceTypeSchema(), locations=('json',))
@swag_from('swagger/create_device_type_spec.yaml')
def post(self, args):
if g.current_account.role_id != 1:
abort(403, message='Only admin may create device types',
status='error')
args = args['device_type']
success = devices.create_device_type(
args['name'])
if success:
@ -96,8 +75,8 @@ class DeviceTypeListResource(ProtectedResource):
@swag_from('swagger/get_device_types_spec.yaml')
def get(self):
return DeviceTypesWrapperSchema().dump(
{'device_types': devices.get_device_types()}), 200
return DeviceTypeSchema().dump(devices.get_device_types(),
many=True), 200
class DeviceRecordingResource(ProtectedResource):
@ -105,13 +84,12 @@ class DeviceRecordingResource(ProtectedResource):
def get(self, device_id):
validate_device_ownership(device_id)
request_args = request.args
return RecordingsWrapperSchema().dump(
{'recordings':
return RecordingsSchema().dump(
devices.get_device_recordings_filtered(
device_id,
request_args.get('record_type'),
request_args.get('start_date'),
request_args.get('end_date'))}), 200
request_args.get('end_date')), many=True), 200
@swag_from('swagger/create_device_recording_spec.yaml')
def post(self, device_id):
@ -122,10 +100,9 @@ class DeviceRecordingResource(ProtectedResource):
class DeviceListResource(ProtectedResource):
@use_args(DeviceWrapperSchema())
@use_args(DeviceSchema(), locations=('json',))
@swag_from('swagger/create_device_spec.yaml')
def post(self, args):
args = args['device']
success = devices.create_device(
args['name'],
g.current_account.id,
@ -135,8 +112,8 @@ class DeviceListResource(ProtectedResource):
@swag_from('swagger/get_devices_spec.yaml')
def get(self):
return DevicesWrapperSchema().dump(
{'devices': devices.get_devices(g.current_account.id)}), 200
return DeviceSchema().dump(
devices.get_devices(g.current_account.id), many=True), 200
class DeviceConfigurationResource(ProtectedResource):

View File

@ -10,10 +10,6 @@ parameters:
required: true
schema:
type: object
required:
- user
properties:
user:
$ref: '#/definitions/User'
security: []
responses:

View File

@ -9,10 +9,6 @@ parameters:
required: true
schema:
type: object
required:
- dashboard
properties:
dashboard:
$ref: '#/definitions/DashboardCreation'
responses:
201:

View File

@ -9,10 +9,6 @@ parameters:
required: true
schema:
type: object
required:
- device
properties:
device:
$ref: '#/definitions/DeviceCreation'
responses:
201:

View File

@ -9,10 +9,6 @@ parameters:
required: true
schema:
type: object
required:
- device_type
properties:
device_type:
$ref: '#/definitions/DeviceType'
responses:
201:

View File

@ -9,10 +9,6 @@ parameters:
required: true
schema:
type: object
required:
- role
properties:
device:
$ref: '#/definitions/Role'
responses:
201:

View File

@ -11,10 +11,6 @@ parameters:
required: true
schema:
type: object
required:
- user
properties:
user:
$ref: '#/definitions/Credentials'
security: []
responses:

View File

@ -15,9 +15,9 @@ responses:
schema:
type: object
required:
- user
- content
properties:
user:
content:
$ref: '#/definitions/User'
403:
description: Accessed a different account

View File

@ -14,8 +14,8 @@ responses:
schema:
type: object
required:
- dashboard
- content
properties:
device:
content:
$ref: '#/definitions/Dashboard'

View File

@ -8,9 +8,9 @@ responses:
schema:
type: object
required:
- dashboards
- content
properties:
devices:
content:
type: array
items:
$ref: '#/definitions/Dashboard'

View File

@ -33,9 +33,9 @@ responses:
schema:
type: object
required:
- recordings
- content
properties:
recordings:
content:
type: array
items:
$ref: '#/definitions/Recording'

View File

@ -14,8 +14,8 @@ responses:
schema:
type: object
required:
- device
- content
properties:
device:
content:
$ref: '#/definitions/DeviceWithConfig'

View File

@ -14,8 +14,8 @@ responses:
schema:
type: object
required:
- device_type
- content
properties:
device_type:
content:
$ref: '#/definitions/DeviceType'

View File

@ -23,9 +23,9 @@ responses:
schema:
type: object
required:
- device_types
- content
properties:
device_types:
content:
type: array
items:
$ref: '#/definitions/DeviceType'

View File

@ -23,9 +23,9 @@ responses:
schema:
type: object
required:
- devices
- content
properties:
devices:
content:
type: array
items:
$ref: '#/definitions/Device'

View File

@ -14,8 +14,8 @@ responses:
schema:
type: object
required:
- role
- content
properties:
device:
content:
$ref: '#/definitions/Role'

View File

@ -8,9 +8,9 @@ responses:
schema:
type: object
required:
- roles
- content
properties:
devices:
content:
type: array
items:
$ref: '#/definitions/Role'

View File

@ -1,24 +1,23 @@
from flask_restful import Resource, abort
from webargs import fields
from marshmallow import Schema
from webargs.flaskparser import use_args
from flasgger import swag_from
from app.api.auth_protection import ProtectedResource
import app.accounts.api as accounts
class TokenResource(Resource):
user_args = {
'user': fields.Nested({
'username': fields.Str(required=True),
'password': fields.Str(required=True)
}, required=True, location='json')
}
class UserInfoSchema(Schema):
username = fields.Str(required=True)
password = fields.Str(required=True)
@use_args(user_args)
class TokenResource(Resource):
@use_args(UserInfoSchema(), locations=('json',))
@swag_from('swagger/create_token_spec.yaml')
def post(self, args):
try:
args = args['user']
token = accounts.create_token(
args['username'],
args['password'])

View File

@ -0,0 +1,7 @@
from marshmallow import Schema, post_dump
class BaseResourceSchema(Schema):
@post_dump(pass_many=True)
def wrap_with_envelope(self, data, many):
return {'content': data}

View File

@ -12,6 +12,7 @@ app.config.from_pyfile('config.py', silent=True)
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
swagger = Swagger(app, template_file='swagger/template.yaml')
swagger.template['info']['version'] = app.config['APP_VERSION']
CORS(app)
celery = celery_configurator.make_celery(app)

View File

@ -2,6 +2,7 @@ import os
# App configuration
DEBUG = os.environ['DEBUG']
APP_VERSION = '0.2.0'
# Define the application directory
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
@ -39,7 +40,7 @@ MQTT_REFRESH_TIME = 1.0 # refresh time in seconds
CELERY_BROKER_URL = os.environ['REDIS_URL']
CELERY_RESULT_BACKEND = os.environ['REDIS_URL']
# Flassger config
# Flasgger config
SWAGGER = {
'uiversion': 3
}