Skip to content

Commit

Permalink
Refs #1. Added database model and started work on user API.
Browse files Browse the repository at this point in the history
  • Loading branch information
SBriere committed Feb 12, 2024
1 parent 3ee23d9 commit 24d4f8e
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 10 deletions.
119 changes: 119 additions & 0 deletions API/user/QueryDashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from flask import request
from flask_restx import Resource, inputs
from sqlalchemy import exc, inspect

from FlaskModule import user_api_ns as api
from libDashboards.db.models.DashDashboards import DashDashboards
from opentera.services.ServiceAccessManager import ServiceAccessManager, current_login_type, LoginType, \
current_user_client
from flask_babel import gettext


# Parser definition(s)
# GET
get_parser = api.parser()
get_parser.add_argument('uuid', type=str, help='Specific dashboard uuid to query information for.')
get_parser.add_argument('id_site', type=int, help='ID of the site to query all dashboards for')
get_parser.add_argument('id_project', type=int, help='ID of the project to query all dashboards for')
get_parser.add_argument('globals', type=inputs.boolean, help='Query globals dashboards')

get_parser.add_argument('all_versions', type=inputs.boolean, help='Return all versions of the dashboard(s)')
get_parser.add_argument('list', type=inputs.boolean, help='Return minimal information (to display in a list, for '
'example)')

# POST
post_schema = api.schema_model('criteria', {'properties': DashDashboards.get_json_schema(), 'type': 'object',
'location': 'json'})

# DELETE
delete_parser = api.parser()
delete_parser.add_argument('id', type=int, help='ID to delete')


class QueryDashboard(Resource):

def __init__(self, _api, *args, **kwargs):
Resource.__init__(self, _api, *args, **kwargs)
self.module = kwargs.get('flaskModule', None)
self.test = kwargs.get('test', False)

@api.doc(description='Get dashboard information. Should specify only one id or the "globals" parameter',
responses={200: 'Success - returns list of dashboards',
400: 'Required parameter is missing',
403: 'Logged user doesn\'t have permission to access the requested data'},
params={'token': 'Secret token'})
@api.expect(get_parser)
@ServiceAccessManager.token_required(allow_static_tokens=False, allow_dynamic_tokens=True)
def get(self):
if current_login_type != LoginType.USER_LOGIN:
return gettext('Only users can use this API.'), 403

# Parse arguments
request_args = get_parser.parse_args(strict=False)

dashboards = []
if 'uuid' in request_args:
dashboard = DashDashboards.get_dashboard_by_uuid(request_args['uuid'])
if not dashboard:
return gettext('Forbidden'), 403 # Explicitely vague for security purpose

if dashboard.id_site:
site_role = current_user_client.get_role_for_site(dashboard.id_site)
if site_role == 'Undefined':
return gettext('Forbidden'), 403
if dashboard.id_project:
project_role = current_user_client.get_role_for_project(dashboard.id_project)
if project_role == 'Undefined':
return gettext('Forbidden'), 403

if not dashboard.id_project and not dashboard.id_site:
# Global dashboard - only for super admins
if not current_user_client.user_superadmin:
return gettext('Forbidden'), 403
dashboards = [dashboard]

elif 'id_site' in request_args:
site_role = current_user_client.get_role_for_site(request_args['id_site'])
if site_role == 'Undefined':
return gettext('Forbidden'), 403
dashboards = DashDashboards.get_dashboards_for_site(request_args['id_site'])

elif 'id_project' in request_args:
project_role = current_user_client.get_role_for_project(request_args['id_project'])
if project_role == 'Undefined':
return gettext('Forbidden'), 403
dashboards = DashDashboards.get_dashboards_for_project(request_args['id_project'])

elif request_args['globals']:
if not current_user_client.user_superadmin:
return gettext('Forbidden'), 403
dashboards = DashDashboards.get_dashboards_globals()
else:
return gettext('Must specify at least one id parameter or "globals"')

# Convert to json and return
dashboards_json = [dash.to_json(request_args['list']) for dash in dashboards]
return dashboards_json

@api.expect(post_schema)
@api.doc(description='Create or update a dashboard',
responses={200: 'Success',
403: 'No access to this API',
400: 'Missing parameter in request'
},
params={'token': 'Secret token'})
@ServiceAccessManager.token_required(allow_static_tokens=False, allow_dynamic_tokens=True)
def post(self):
if current_login_type != LoginType.USER_LOGIN:
return gettext('Only users can use this API.'), 403

@api.expect(delete_parser, validate=True)
@api.doc(description='Delete a dashboard',
responses={200: 'Success - deleted',
400: 'Bad request',
403: 'Access denied'},
params={'token': 'Secret token'})
@ServiceAccessManager.token_required(allow_static_tokens=False, allow_dynamic_tokens=True)
def delete(self):
if current_login_type != LoginType.USER_LOGIN:
return gettext('Only users can use this API.'), 403
2 changes: 1 addition & 1 deletion DashboardsService.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ def notify_service_messages(self, pattern, channel, message):
'correctly set in this service?')
sys.exit(1)


service_info = json.loads(service_info)
if 'service_uuid' not in service_info:
sys.stderr.write('OpenTera Server didn\'t return a valid service UUID - aborting.')
Expand Down Expand Up @@ -94,6 +93,7 @@ def notify_service_messages(self, pattern, channel, message):
Globals.db_man.open_local(None, echo=True)
else:
Globals.db_man.open(POSTGRES, Globals.config_man.service_config['debug_mode'])
Globals.db_man.create_defaults(True)
except OperationalError as e:
print("Unable to connect to database - please check settings in config file!", e)
quit()
Expand Down
9 changes: 3 additions & 6 deletions FlaskModule.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def base_path(self):
}

# API
api = CustomAPI(flask_app, version='1.0.0', title='OMRService API',
api = CustomAPI(flask_app, version='1.0.0', title='DashboardsService API',
description='DasiboardsService API Documentation', doc='/doc', prefix='/api',
authorizations=authorizations)

Expand Down Expand Up @@ -172,7 +172,6 @@ def __init__(self, config: ConfigManager, service):
flask_app.config.update({'BABEL_DEFAULT_LOCALE': 'fr'})
flask_app.config.update({'SESSION_COOKIE_SECURE': True})


# Init API
self.init_service_api(self, self.service, service_api_ns)
self.init_user_api(self, self.service, user_api_ns)
Expand Down Expand Up @@ -228,14 +227,14 @@ def init_service_api(module: object, service: object, api_ns=service_api_ns, add
# Default arguments
kwargs = {'flaskModule': module, 'service': service} | additional_args


@staticmethod
def init_user_api(module: object, service: object, api_ns=user_api_ns, additional_args={}):
# Default arguments
kwargs = {'flaskModule': module, 'service': service} | additional_args

pass
from API.user.QueryDashboard import QueryDashboard

api_ns.add_resource(QueryDashboard, '/dashboards', resource_class_kwargs=kwargs)

@staticmethod
def init_participant_api(module: object, service: object, api_ns=participant_api_ns, additional_args={}):
Expand All @@ -244,8 +243,6 @@ def init_participant_api(module: object, service: object, api_ns=participant_api

pass



def init_views(self):
# Default arguments
args = []
Expand Down
9 changes: 6 additions & 3 deletions libDashboards/db/DBManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ def __init__(self, app=flask_app, test: bool = False):
self.app = app
self.test = test

def create_defaults(self, config: ConfigManager, test=False):
pass
def create_defaults(self, test=False):
with self.app.app_context():
from libDashboards.db.models.DashDashboards import DashDashboards
if DashDashboards.get_count() == 0:
print("No dashboards - creating defaults")
DashDashboards.create_defaults(test)

def open(self, db_infos, echo=False):
self.db_uri = 'postgresql://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s' % db_infos
Expand All @@ -46,7 +50,6 @@ def open(self, db_infos, echo=False):
# Create db engine
self.db.init_app(self.app)
self.db.app = self.app

BaseModel.set_db(self.db)

with self.app.app_context():
Expand Down
102 changes: 102 additions & 0 deletions libDashboards/db/models/DashDashboards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from libDashboards.db.models.BaseModel import BaseModel
from sqlalchemy import Column, Integer, Sequence, String, Boolean
import uuid


class DashDashboards(BaseModel):
__tablename__ = 't_dashboards'
id_dashboard = Column(Integer, Sequence('id_dashboard_sequence'), primary_key=True, autoincrement=True)
id_site = Column(Integer, nullable=True)
id_project = Column(Integer, nullable=True)

dashboard_uuid = Column(String(36), nullable=False)
dashboard_name = Column(String, nullable=False)
dashboard_enabled = Column(Boolean, nullable=False, default=True)
dashboard_description = Column(String, nullable=True) # Dashboard user-visible description
dashboard_definition = Column(String, nullable=False) # Dashboard definition string
dashboard_version = Column(Integer, nullable=False, default=1)

def to_json(self, ignore_fields=None, minimal=False):
if ignore_fields is None:
ignore_fields = []

if minimal:
ignore_fields.extend(['asset_definition'])

asset_json = super().to_json(ignore_fields=ignore_fields)

return asset_json

@staticmethod
def get_dashboard_by_uuid(dashboard_uuid: str, latest=True):
query = DashDashboards.query.filter_by(dashboard_uuid=dashboard_uuid)
if latest:
query = query.order_by(DashDashboards.dashboard_version).desc()

return query.first()

@staticmethod
def get_dashboards_for_site(site_id: int, latest=True) -> []:
return DashDashboards.query.filter_by(id_site=site_id).all()

@staticmethod
def get_dashboards_for_project(project_id: int, latest=True) -> []:
query = DashDashboards.query.filter_by(id_project=project_id)
# if latest:
# query = query.group_by(DashDashboards.dashboard_uuid)
return query.all()

@staticmethod
def get_dashboards_globals(latest=True) -> []:
return DashDashboards.query.filter_by(id_project=None, id_site=None).all()

@classmethod
def insert(cls, dashboard):
# Generate UUID
if not dashboard.dashboard_uuid:
dashboard.dashboard_uuid = str(uuid.uuid4())

super().insert(dashboard)

@staticmethod
def create_defaults(test=False):
if test:
# Create dashboard for site...
dashboard = DashDashboards()
dashboard.id_site = 1
dashboard.dashboard_name = 'Site 1 - Global'
dashboard.dashboard_description = 'Test dashboard for global site overview'
dashboard.dashboard_definition = '{}'
DashDashboards.insert(dashboard)

# ... for project...
dashboard = DashDashboards()
dashboard.id_project = 1
dashboard.dashboard_name = 'Project 1 - Global'
dashboard.dashboard_description = 'Test dashboard for global project overview'
dashboard.dashboard_definition = '{}'
DashDashboards.insert(dashboard)

dashboard = DashDashboards()
dashboard.id_project = 1
dashboard.dashboard_name = 'Project 1 - Alerts'
dashboard.dashboard_description = 'Test dashboard for project alerts'
dashboard.dashboard_definition = '{}'
DashDashboards.insert(dashboard)
uuid = dashboard.dashboard_uuid

dashboard = DashDashboards()
dashboard.id_project = 1
dashboard.dashboard_name = 'Project 1 - Alerts v2'
dashboard.dashboard_description = 'Test dashboard for project alerts'
dashboard.dashboard_definition = '{}'
dashboard.dashboard_version = 2
dashboard.dashboard_uuid = uuid
DashDashboards.insert(dashboard)

# ... and globals
dashboard = DashDashboards()
dashboard.dashboard_name = 'System Dashboard'
dashboard.dashboard_description = 'Global system dashboard'
dashboard.dashboard_definition = '{}'
DashDashboards.insert(dashboard)
6 changes: 6 additions & 0 deletions libDashboards/db/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .DashDashboards import DashDashboards

# All exported symbols
__all__ = [
'DashDashboards'
]

0 comments on commit 24d4f8e

Please sign in to comment.