From e9ebff8be6684ea7b1faed9c02ca2640245c6d2a Mon Sep 17 00:00:00 2001 From: esensar Date: Mon, 8 Oct 2018 21:50:34 +0200 Subject: [PATCH] Standardize all responses and requests * Remove root object from requests * Add "content" wrapper for all responses --- app/api/resources/account.py | 39 +++-------- app/api/resources/dashboard.py | 28 +++----- app/api/resources/device.py | 65 ++++++------------- .../swagger/create_account_spec.yaml | 6 +- .../swagger/create_dashboard_spec.yaml | 6 +- .../resources/swagger/create_device_spec.yaml | 6 +- .../swagger/create_device_type_spec.yaml | 6 +- .../resources/swagger/create_role_spec.yaml | 6 +- .../resources/swagger/create_token_spec.yaml | 6 +- .../resources/swagger/get_account_spec.yaml | 4 +- .../resources/swagger/get_dashboard_spec.yaml | 4 +- .../swagger/get_dashboards_spec.yaml | 4 +- .../swagger/get_device_recordings_spec.yaml | 4 +- .../resources/swagger/get_device_spec.yaml | 4 +- .../swagger/get_device_type_spec.yaml | 4 +- .../swagger/get_device_types_spec.yaml | 4 +- .../resources/swagger/get_devices_spec.yaml | 4 +- app/api/resources/swagger/get_role_spec.yaml | 4 +- app/api/resources/swagger/get_roles_spec.yaml | 4 +- app/api/resources/token.py | 17 +++-- app/api/schemas.py | 7 ++ 21 files changed, 80 insertions(+), 152 deletions(-) create mode 100644 app/api/schemas.py diff --git a/app/api/resources/account.py b/app/api/resources/account.py index 9868f47..6cf9b99 100644 --- a/app/api/resources/account.py +++ b/app/api/resources/account.py @@ -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'], diff --git a/app/api/resources/dashboard.py b/app/api/resources/dashboard.py index ae3652f..55f308b 100644 --- a/app/api/resources/dashboard.py +++ b/app/api/resources/dashboard.py @@ -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 diff --git a/app/api/resources/device.py b/app/api/resources/device.py index 1c6d8e1..bf23d98 100644 --- a/app/api/resources/device.py +++ b/app/api/resources/device.py @@ -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): diff --git a/app/api/resources/swagger/create_account_spec.yaml b/app/api/resources/swagger/create_account_spec.yaml index 99190bc..0d94b40 100644 --- a/app/api/resources/swagger/create_account_spec.yaml +++ b/app/api/resources/swagger/create_account_spec.yaml @@ -10,11 +10,7 @@ parameters: required: true schema: type: object - required: - - user - properties: - user: - $ref: '#/definitions/User' + $ref: '#/definitions/User' security: [] responses: 201: diff --git a/app/api/resources/swagger/create_dashboard_spec.yaml b/app/api/resources/swagger/create_dashboard_spec.yaml index 6314787..82a48ee 100644 --- a/app/api/resources/swagger/create_dashboard_spec.yaml +++ b/app/api/resources/swagger/create_dashboard_spec.yaml @@ -9,11 +9,7 @@ parameters: required: true schema: type: object - required: - - dashboard - properties: - dashboard: - $ref: '#/definitions/DashboardCreation' + $ref: '#/definitions/DashboardCreation' responses: 201: description: Successful creation diff --git a/app/api/resources/swagger/create_device_spec.yaml b/app/api/resources/swagger/create_device_spec.yaml index 900789a..df480b8 100644 --- a/app/api/resources/swagger/create_device_spec.yaml +++ b/app/api/resources/swagger/create_device_spec.yaml @@ -9,11 +9,7 @@ parameters: required: true schema: type: object - required: - - device - properties: - device: - $ref: '#/definitions/DeviceCreation' + $ref: '#/definitions/DeviceCreation' responses: 201: description: Successful creation diff --git a/app/api/resources/swagger/create_device_type_spec.yaml b/app/api/resources/swagger/create_device_type_spec.yaml index 7433cf7..373705e 100644 --- a/app/api/resources/swagger/create_device_type_spec.yaml +++ b/app/api/resources/swagger/create_device_type_spec.yaml @@ -9,11 +9,7 @@ parameters: required: true schema: type: object - required: - - device_type - properties: - device_type: - $ref: '#/definitions/DeviceType' + $ref: '#/definitions/DeviceType' responses: 201: description: Successful creation diff --git a/app/api/resources/swagger/create_role_spec.yaml b/app/api/resources/swagger/create_role_spec.yaml index 2ca95d8..e5e9fd5 100644 --- a/app/api/resources/swagger/create_role_spec.yaml +++ b/app/api/resources/swagger/create_role_spec.yaml @@ -9,11 +9,7 @@ parameters: required: true schema: type: object - required: - - role - properties: - device: - $ref: '#/definitions/Role' + $ref: '#/definitions/Role' responses: 201: description: Successful creation diff --git a/app/api/resources/swagger/create_token_spec.yaml b/app/api/resources/swagger/create_token_spec.yaml index 08ebb56..59a39b7 100644 --- a/app/api/resources/swagger/create_token_spec.yaml +++ b/app/api/resources/swagger/create_token_spec.yaml @@ -11,11 +11,7 @@ parameters: required: true schema: type: object - required: - - user - properties: - user: - $ref: '#/definitions/Credentials' + $ref: '#/definitions/Credentials' security: [] responses: 200: diff --git a/app/api/resources/swagger/get_account_spec.yaml b/app/api/resources/swagger/get_account_spec.yaml index 217d865..edb29ea 100644 --- a/app/api/resources/swagger/get_account_spec.yaml +++ b/app/api/resources/swagger/get_account_spec.yaml @@ -15,9 +15,9 @@ responses: schema: type: object required: - - user + - content properties: - user: + content: $ref: '#/definitions/User' 403: description: Accessed a different account diff --git a/app/api/resources/swagger/get_dashboard_spec.yaml b/app/api/resources/swagger/get_dashboard_spec.yaml index 29dce88..1ecc21c 100644 --- a/app/api/resources/swagger/get_dashboard_spec.yaml +++ b/app/api/resources/swagger/get_dashboard_spec.yaml @@ -14,8 +14,8 @@ responses: schema: type: object required: - - dashboard + - content properties: - device: + content: $ref: '#/definitions/Dashboard' diff --git a/app/api/resources/swagger/get_dashboards_spec.yaml b/app/api/resources/swagger/get_dashboards_spec.yaml index 253c61e..568605a 100644 --- a/app/api/resources/swagger/get_dashboards_spec.yaml +++ b/app/api/resources/swagger/get_dashboards_spec.yaml @@ -8,9 +8,9 @@ responses: schema: type: object required: - - dashboards + - content properties: - devices: + content: type: array items: $ref: '#/definitions/Dashboard' diff --git a/app/api/resources/swagger/get_device_recordings_spec.yaml b/app/api/resources/swagger/get_device_recordings_spec.yaml index 9387395..9733a5f 100644 --- a/app/api/resources/swagger/get_device_recordings_spec.yaml +++ b/app/api/resources/swagger/get_device_recordings_spec.yaml @@ -33,9 +33,9 @@ responses: schema: type: object required: - - recordings + - content properties: - recordings: + content: type: array items: $ref: '#/definitions/Recording' diff --git a/app/api/resources/swagger/get_device_spec.yaml b/app/api/resources/swagger/get_device_spec.yaml index 76fab55..2120137 100644 --- a/app/api/resources/swagger/get_device_spec.yaml +++ b/app/api/resources/swagger/get_device_spec.yaml @@ -14,8 +14,8 @@ responses: schema: type: object required: - - device + - content properties: - device: + content: $ref: '#/definitions/DeviceWithConfig' diff --git a/app/api/resources/swagger/get_device_type_spec.yaml b/app/api/resources/swagger/get_device_type_spec.yaml index 26bf588..47f1aa5 100644 --- a/app/api/resources/swagger/get_device_type_spec.yaml +++ b/app/api/resources/swagger/get_device_type_spec.yaml @@ -14,8 +14,8 @@ responses: schema: type: object required: - - device_type + - content properties: - device_type: + content: $ref: '#/definitions/DeviceType' diff --git a/app/api/resources/swagger/get_device_types_spec.yaml b/app/api/resources/swagger/get_device_types_spec.yaml index 2caa5a6..ee33223 100644 --- a/app/api/resources/swagger/get_device_types_spec.yaml +++ b/app/api/resources/swagger/get_device_types_spec.yaml @@ -23,9 +23,9 @@ responses: schema: type: object required: - - device_types + - content properties: - device_types: + content: type: array items: $ref: '#/definitions/DeviceType' diff --git a/app/api/resources/swagger/get_devices_spec.yaml b/app/api/resources/swagger/get_devices_spec.yaml index 1dd9789..a702f32 100644 --- a/app/api/resources/swagger/get_devices_spec.yaml +++ b/app/api/resources/swagger/get_devices_spec.yaml @@ -23,9 +23,9 @@ responses: schema: type: object required: - - devices + - content properties: - devices: + content: type: array items: $ref: '#/definitions/Device' diff --git a/app/api/resources/swagger/get_role_spec.yaml b/app/api/resources/swagger/get_role_spec.yaml index 38defde..84c0358 100644 --- a/app/api/resources/swagger/get_role_spec.yaml +++ b/app/api/resources/swagger/get_role_spec.yaml @@ -14,8 +14,8 @@ responses: schema: type: object required: - - role + - content properties: - device: + content: $ref: '#/definitions/Role' diff --git a/app/api/resources/swagger/get_roles_spec.yaml b/app/api/resources/swagger/get_roles_spec.yaml index 6c8f87a..274463b 100644 --- a/app/api/resources/swagger/get_roles_spec.yaml +++ b/app/api/resources/swagger/get_roles_spec.yaml @@ -8,9 +8,9 @@ responses: schema: type: object required: - - roles + - content properties: - devices: + content: type: array items: $ref: '#/definitions/Role' diff --git a/app/api/resources/token.py b/app/api/resources/token.py index 16d3b2e..b0bba45 100644 --- a/app/api/resources/token.py +++ b/app/api/resources/token.py @@ -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']) diff --git a/app/api/schemas.py b/app/api/schemas.py new file mode 100644 index 0000000..8f4a228 --- /dev/null +++ b/app/api/schemas.py @@ -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}