"""Application Handlers. Requests are redirected to handlers, which are responsible for getting information from the URL and passing it down to the logic layer. The way each layer talks to each other is through Response objects which defines the type status of the data and the data itself. Please note: the Orchard uses the term handlers over views as convention for clarity See: oto.response for more details. """ from flask import g from flask import jsonify from flask import request from oto import response from oto.adaptors.flask import flaskify from conflict_manager import config from conflict_manager import exceptions from conflict_manager.api import app from conflict_manager.constants import auth as auth_consts from conflict_manager.constants import error as error_consts from conflict_manager.constants import schema as schema_consts from conflict_manager.logic import action as action_logic from conflict_manager.logic import conflict as conflict_logic from conflict_manager.logic import conflict_status as conflict_status_logic from conflict_manager.schemas.handlers import conflict_status from conflict_manager.schemas.handlers import post_action from conflict_manager.utils import account_utils from conflict_manager.utils import handler_utils @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.errorhandler(exceptions.RequestError) def handle_validation_error(error): """Handle invalid pagination errors.""" return error.make_flask_response() @app.errorhandler(500) def exception_handler(error): """Handle error when uncaught exception is raised. Default exception handler. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: flask.Response: A 500 response with JSON 'code' & 'message' payload. """ message = ( 'The server encountered an internal error ' 'and was unable to complete your request.') g.log.exception(error) return flaskify(response.create_fatal_response(message)) @app.errorhandler(504) def timeout_exception_handler(error): """Handle error when timeout exception is raised. Timeout exception handler. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: flask.Response: A 504 response with JSON 'code' & 'message' payload. """ message = ( 'The server encountered a response timeout ' 'and was unable to complete your request.') g.log.exception(error) return flaskify(response.create_error_response( code=error_consts.ERROR_CODE_BAD_GATEWAY, message=message, status=504)) @app.route('/conflicts/new', methods=['GET']) @account_utils.account_required def get_new_conflicts(): """Get list of all new conflicts. Source is `fact_conflict` table. This endpoint is meant to provide the "Open Conflicts" within Workstation. Returns: flask.Response: A 200 response with JSON payload containing list of conflicts matching the passed query params. """ result = conflict_logic.get_new_conflicts( account=account_utils.get_account(), args=request.args) return flaskify(result, encoder=handler_utils.DateJSONEncoder) @app.route('/conflicts/actioned', methods=['GET']) @account_utils.account_required def get_actioned_conflicts(): """Get list of all actioned conflicts. Args: request_params (dict): validated request parameters This endpoint is meant to provide the “Actioned Conflicts” view within Workstation. """ result = conflict_logic.get_actioned_conflicts( account=account_utils.get_account()) return flaskify(result, encoder=handler_utils.DateJSONEncoder) @app.route('/conflicts/resolved', methods=['GET']) @account_utils.account_required def get_resolved_conflicts(): """Get list of all resolved conflicts. This endpoint is meant to provide the "Resolved Conflicts" within Workstation. """ result = conflict_logic.get_resolved_conflicts( account=account_utils.get_account()) return flaskify(result, encoder=handler_utils.DateJSONEncoder) @app.route('/action', methods=['POST']) @account_utils.account_required @handler_utils.parse_and_validate_request_json( post_action.PostActionPayloadSchema) def create_actions(json_data): """Create an action taken by Workstation user. This endpoint is meant to handle the action request in when the user finishes the “Resolve” workflow. """ orchard_user_id = account_utils.get_user_id() result = action_logic.create_actions( account=account_utils.get_account(), actions_data=json_data, orchard_user_id=orchard_user_id) return flaskify(result) @app.route('/action/bulk', methods=['POST']) @account_utils.account_required @handler_utils.parse_and_validate_request_json( post_action.BulkPostActionPayloadSchema) def bulk_create_actions(json_data): """Create multiple actions taken by Workstation user. This endpoint is meant to handle the action request in when the user finishes the “Bulk Respond” workflow. """ payload_actions = json_data[schema_consts.ACTIONS] orchard_user_id = account_utils.get_user_id() result = action_logic.bulk_create_actions( account=account_utils.get_account(), payload_actions=payload_actions, orchard_user_id=orchard_user_id) return flaskify(result) @app.route('/conflicts/status/bulk', methods=['POST']) @handler_utils.parse_and_validate_request_json( conflict_status.BulkUpdateConflictStatusSchema) def bulk_update_conflict_status(json_data): """Update grouped conflict status. This endpoint is meant to provide the "Update External Conflict Status" within OA. """ user_id = account_utils.get_user_id() account = account_utils.get_account() prefix = '' if user_id: validation_response = account_utils.validate_user_id(user_id) if not validation_response: return flaskify(validation_response) prefix, user_id = user_id.split(':') prefix = prefix.lower() if (prefix != auth_consts.OA_USER_PREFIX): user_id = None validation_response = account_utils._validate_authorization_info( account.type, account.id) if not validation_response: return flaskify(validation_response) status = json_data[schema_consts.STATUS].upper() grouped_conflicts_ids = json_data[schema_consts.GROUPED_CONFLICTS_IDS] note = json_data.get(schema_consts.NOTE) result = conflict_status_logic.bulk_update_conflict_status( user_id=user_id, grouped_conflicts_ids=grouped_conflicts_ids, status=status, note=note, account_type=account.type, account_id=account.id) return flaskify(result)