"""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. """ import json from flask import g from flask import jsonify from flask import request from oto import response from oto.adaptors.flask import flaskify from sales_goals import config from sales_goals.api import app from sales_goals.constants import error from sales_goals.constants import header from sales_goals.constants.models import MARKETING_PROGRAM_ENTITY_TYPES from sales_goals.logic import countries from sales_goals.logic import digital_projections from sales_goals.logic import marketing_highlights from sales_goals.logic import marketing_program_info from sales_goals.logic import ownership from sales_goals.logic import sales_goals as sales_goals_logic from sales_goals.utils import handler_utils from sales_goals.validation.validation import json_validator, validate PATCH_SALES_GOALS_VALIDATOR = json_validator(config.PATCH_SALES_GOALS_SCHEMA) POST_SALES_GOALS_VALIDATOR = json_validator(config.POST_SALES_GOALS_SCHEMA) POST_SALES_GOALS_TERRITORY_VALIDATOR = json_validator( config.POST_SALES_GOALS_TERRITORY_SCHEMA) @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.errorhandler(500) def exception_handler(error): """Default handler when uncaught exception is raised. 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.route('/goals/', methods=['GET']) def sales_goal_fetch(product_id): """Fetch an existing goal by it's product_id. Args: product_id (int): product_id for the goal we want to fetch. Returns: flask.Response: JSON object representing the sales goal. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) user_id = request.headers.get(header.ORCHARD_USER_ID) result = sales_goals_logic.fetch_by_product_id( product_id=product_id, account_type=account_type, account_id=account_id, user_id=user_id) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/goals/', methods=['POST']) def sales_goal_create(product_id): """Create the new goal for an existing product given product_id. Args: product_id (int): product_id for the goal to be created. Returns: flask.Response: JSON object representing the sales goal. """ data = request.get_json() validation_response = validate(data, POST_SALES_GOALS_VALIDATOR) if not validation_response: return flaskify(validation_response) account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) user_id = request.headers.get(header.ORCHARD_USER_ID) result = sales_goals_logic.create_for_product_id( product_id=product_id, sales_goal_data=data, account_type=account_type, account_id=account_id, user_id=user_id) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/goals/countries', methods=['GET']) def countries_get(): """Get all the countries which it is possible to specify the goals for. Returns: flask.Response: JSON object containing the list of countries. """ user_id = request.headers.get(header.ORCHARD_USER_ID) result = countries.get_countries(user_id=user_id) return flaskify(result) @app.route('/territories', methods=['GET']) def get_territories(): """Get all the countries and markets. Returns: flask.Response: JSON object containing the list of countries. """ result = countries.get_territories() return flaskify(result) @app.route('/goals/', methods=['PATCH']) def update_sales_goal_patch(product_id): """Update sales goal, but only for fields passed in. Args: product_id (int): product_id for the goal to be updated. Returns: flask.Response: JSON object representing the sales goal. """ data = request.get_json() validation_response = validate(data, PATCH_SALES_GOALS_VALIDATOR) if not validation_response: return flaskify(validation_response) account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) user_id = request.headers.get(header.ORCHARD_USER_ID) result = sales_goals_logic.update_for_product_id( product_id=product_id, sales_goal_data=data, account_type=account_type, account_id=account_id, user_id=user_id) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/goals//', methods=['POST']) def target_market_goal_create(product_id, country_id): """Create target market goal for a goal by given product_id and country_id. Args: product_id (int): product_id for the new target market goal. country_id (int): country_id for the new target market goal. Returns: flask.Response: JSON object representing the target market goal. """ data = request.get_json() validation_response = validate(data, POST_SALES_GOALS_TERRITORY_VALIDATOR) if not validation_response: return flaskify(validation_response) account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) user_id = request.headers.get(header.ORCHARD_USER_ID) result = sales_goals_logic.create_target_market_goal( product_id=product_id, country_id=country_id, market_goal_data=data, account_type=account_type, account_id=account_id, user_id=user_id) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/goals//', methods=['DELETE']) def target_market_goal_delete(product_id, country_id): """Delete an existing goal by given product_id and country_id. Args: product_id (int): product_id for the target market goal that should be deleted. country_id (int): country_id for the target market goal that should be deleted. Returns: flask.Response: JSON object with ID of deleted target market goal. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) user_id = request.headers.get(header.ORCHARD_USER_ID) result = sales_goals_logic.delete_target_market_goal( product_id=product_id, country_id=country_id, account_type=account_type, account_id=account_id, user_id=user_id) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/marketing_highlights/', methods=['POST']) def create_marketing_highlight(project_id): """Create a marketing highlight for the project. Args: project_id (int): project_id for the marketing highlight Returns: flask.Response: JSON object with the marketing highlight. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) result = marketing_highlights.create_project_marketing_highlights( project_id=project_id, data=request.get_json()) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/marketing_highlights/', methods=['PUT']) def upsert_marketing_highlights(project_id): """Upsert marketing highlights for the project. Args: project_id (int): project_id for the marketing highlight Returns: flask.Response: JSON object with the marketing highlight. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) result = marketing_highlights.upsert_project_marketing_highlights( project_id=project_id, data=request.get_json() ) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/marketing_highlights/', methods=['GET']) def get_marketing_highlight(project_id): """Get the marketing highlights for the project. Args: project_id (int): project_id for the marketing highlight Returns: flask.Response: JSON object with the marketing highlights. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) result = marketing_highlights.get_project_marketing_highlights( project_id=project_id) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route( '/marketing_highlights//', methods=['DELETE'] ) def delete_marketing_highlight(project_id, territory_id): """Get the marketing highlights for the project. Args: project_id (int): project_id for the marketing highlight territory_id (int): territory_id for the marketing highlight Returns: flask.Response: JSON object that marks success or failure """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) result = marketing_highlights.delete_project_marketing_highlight( project_id=project_id, territory_id=territory_id) return flaskify(result, encoder=handler_utils.DatetimeEncoder) @app.route('/digital/', methods=['GET']) def get_digital_projections(product_id): """Fetch product projections. Args: product_id (int): Product id. Returns: flask.Response: on successful, 200 status with JSON body. """ ownership_response = get_product_id_ownership_response(request, product_id) if not ownership_response: return flaskify(ownership_response) orchard_user_id = request.headers.get(header.ORCHARD_USER_ID, '') return flaskify( digital_projections.get_digital_projections_by_product_id( product_id, orchard_user_id )) @app.route('/digital/projections/dataloader', methods=['POST']) def dataload_digital_product_projections(): """Dataload digital product marketing projections/priorities. Returns: flask.Response: on successful, 200 status with JSON body. """ product_ids = request.get_json() if not isinstance(product_ids, list) or not all( isinstance(item, int) for item in product_ids ): return flaskify( response.create_error_response( error.ERROR_CODE_INVALID_INPUT, error.ERROR_MESSAGE_INVALID_DATALOAD ) ) return flaskify( digital_projections.dataload_digital_product_projections( product_ids )) @app.route('/digital/', methods=['POST']) def upsert_digital_global_projections(product_id): """Create or update projections for a product. Args: product_id (int): Product id. Returns: flask.Response: on successful, 200 status with JSON body. """ ownership_response = get_product_id_ownership_response(request, product_id) if not ownership_response: return flaskify(ownership_response) orchard_user_id = request.headers.get(header.ORCHARD_USER_ID, '') return flaskify(digital_projections.upsert_global_projections( product_id, request.get_json(), orchard_user_id)) @app.route('/digital//projections', methods=['POST', 'PUT']) def upsert_digital_territory_projections(product_id): """Upsert projections for a product and territory. Args: product_id (int): Product id. Returns: flask.Response: on successful, 200 status with JSON body. """ delete = request.method == 'PUT' ownership_response = get_product_id_ownership_response(request, product_id) if not ownership_response: return flaskify(ownership_response) orchard_user_id = None if header.ORCHARD_USER_ID in request.headers: orchard_user_id = request.headers[header.ORCHARD_USER_ID] return flaskify(digital_projections.set_marketing_priority_for_product( product_id, request.get_json(), orchard_user_id, delete=delete, )) @app.route('/digital//projection/', methods=['DELETE']) def delete_digital_territory_projection(product_id, projection_id): """Create or update projections for a product and territory. Args: product_id (int): Product id. projection_id (int): Projection id. Returns: flask.Response: on successful, 200 status with JSON body. """ ownership_response = get_product_id_ownership_response(request, product_id) if not ownership_response: return flaskify(ownership_response) return flaskify(digital_projections.delete_territory_projection( product_id, projection_id)) def get_product_id_ownership_response(request, product_id): """Find out if the requester owns the product.""" account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) return ownership.check_product_ownership( product_id, account_type, account_id) @app.route('/marketing_program_info', methods=['POST']) def create_marketing_program_info(): """Create marketing program info.""" payload = request.get_json() user_id = request.headers.get(header.ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code='authorization_error', message='Failed to identify user', status=403 )) marketing_program_info.create_marketing_program_info(payload.get('data')) return flaskify( response.Response(status=200), encoder=handler_utils.DatetimeEncoder) @app.route( '/marketing_program_info//', methods=['GET']) def get_marketing_program_info(entity_type, entity_id): """GET marketing program info of all program types for entity type & id.""" user_id = request.headers.get(header.ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code='authorization_error', message='Failed to identify user', status=403 )) if entity_type not in MARKETING_PROGRAM_ENTITY_TYPES: return flaskify(response.create_error_response( code='bad_request', message='Invalid entity type', status=404 )) result = marketing_program_info.get_marketing_program_info(entity_type, entity_id) return flaskify( response.Response(message=json.dumps(result), status=200), encoder=handler_utils.DatetimeEncoder) @app.route( '/marketing_program_info///program/', methods=['GET']) def get_marketing_program_info_by_program(entity_type, entity_id, program_id): """GET marketing program info of a program type for entity type & id.""" user_id = request.headers.get(header.ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code='authorization_error', message='Failed to identify user', status=403 )) if entity_type not in MARKETING_PROGRAM_ENTITY_TYPES: return flaskify(response.create_error_response( code='bad_request', message='Invalid entity type', status=404 )) if program_id < 1 or program_id > 22: return flaskify(response.create_error_response( code='bad_request', message='Invalid program id', status=404 )) result = marketing_program_info.get_marketing_program_info(entity_type, entity_id, program_id) return flaskify( response.Response(message=json.dumps(result), status=200), encoder=handler_utils.DatetimeEncoder) @app.route( '/marketing_program_info//', methods=['GET']) def get_product_marketing_program_info_by_program(product_id, program_id): """Get product marketing program info by program type.""" user_id = request.headers.get(header.ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code='authorization_error', message='Failed to identify user', status=403 )) result = marketing_program_info.get_marketing_program_info( 'product', product_id, program_id) return flaskify( response.Response(message=json.dumps(result), status=200), encoder=handler_utils.DatetimeEncoder) @app.route( '/marketing_program_info/', methods=['PUT']) def update_marketing_program_info(marketing_program_info_id): """Update marketing program info.""" payload = request.get_json() user_id = request.headers.get(header.ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code='authorization_error', message='Failed to identify user', status=403 )) marketing_program_info.update_marketing_program_info( marketing_program_info_id, payload) return flaskify( response.Response(status=200), encoder=handler_utils.DatetimeEncoder) @app.route( '/marketing_program_info//', methods=['PUT']) def upsert_marketing_program_info(entity_type, entity_id): """Upsert marketing program info for the entity with given type and id.""" user_id = request.headers.get(header.ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code='authorization_error', message='Failed to identify user', status=403 )) if entity_type not in MARKETING_PROGRAM_ENTITY_TYPES: return flaskify(response.create_error_response( code='bad_request', message='Invalid entity type', status=404 )) payload = request.get_json() result = marketing_program_info.upsert_marketing_program_info( entity_type, entity_id, payload) return flaskify( response.Response(message=json.dumps(result), status=200), encoder=handler_utils.DatetimeEncoder) @app.route('/project//projections', methods=['POST', 'PUT']) def upsert_project_territory_projections(project_id): """Create or update projections for a project and territory. Args: project_id (int): Project id. Returns: flask.Response: on successful, 200 status with JSON body. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) delete = request.method == 'PUT' ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) orchard_user_id = None if header.ORCHARD_USER_ID in request.headers: orchard_user_id = request.headers[header.ORCHARD_USER_ID] return flaskify(digital_projections.upsert_project_territory_projections( project_id, request.get_json(), orchard_user_id, delete=delete, )) @app.route('/project/', methods=['GET']) def get_project_marketing_projections(project_id): """Fetch project marketing projections. Args: project_id (int): Project id. Returns: flask.Response: on successful, 200 status with JSON body. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) return flaskify( digital_projections.get_territory_priorities_for_project(project_id)) @app.route('/project/projections/dataloader', methods=['POST']) def dataload_project_marketing_projections(): """Dataload project marketing projections/priorities. Returns: flask.Response: on successful, 200 status with JSON body. """ project_ids = request.get_json() if not isinstance(project_ids, list) or not all( isinstance(item, int) for item in project_ids ): return flaskify( response.create_error_response( error.ERROR_CODE_INVALID_INPUT, error.ERROR_MESSAGE_INVALID_DATALOAD ) ) return flaskify( digital_projections.dataload_project_marketing_priorities( project_ids )) @app.route('/project//projection/', methods=['DELETE']) def delete_project_territory_projection(project_id, projection_id): """DELETE projections for a project and territory. Args: project_id (int): Project id. projection_id (int): Projection id. Returns: flask.Response: on successful, 200 status. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) return flaskify(digital_projections.delete_project_territory_projection( project_id, projection_id)) @app.route('/project/', methods=['POST']) def upsert_project_global_projections(project_id): """Create or update projections for a project. Args: project_id (int): Project id. Returns: flask.Response: on successful, 200 status with JSON body. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) ownership_response = ownership.check_project_ownership( project_id, account_type, account_id) if not ownership_response: return flaskify(ownership_response) orchard_user_id = request.headers.get(header.ORCHARD_USER_ID, '') return flaskify(digital_projections.upsert_global_projections_for_project( project_id, request.get_json(), orchard_user_id))