""" 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: contracts.response for more details. """ from flask import g, request from owsrequest import flask_request from contracts import config, response from contracts.api import app from contracts.constants import error, header, query from contracts.logic import ( account, advances, healthcheck, mech_admin, service_types, vendor_contracts, ) from contracts.models import subaccount_royalty_collection @app.route(config.HEALTH_CHECK, methods=['GET']) def hello(): """Check the health of the application. Returns: flask.Response: the flask response. """ return response.flaskify(healthcheck.status()) @app.route(config.DB_HEALTH_CHECK) def hello_db(): """DB healthcheck. Returns: flask.Response: the flask response. """ return response.flaskify(healthcheck.db_status()) @app.route('/mechadmin', methods=['HEAD']) def get_mechadmin(): """Check whether grass authenticated user is mechadmin. Returns: flask.Response: Status code in response header. """ grass_account_type, grass_account_id = flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=True, vendor=grass_account_id, subaccount=grass_account_id ) if not validation: return response.flaskify(validation) is_mech_admin_result = mech_admin.is_mech_admin( grass_account_type, grass_account_id ) return response.flaskify(response.Response(status=is_mech_admin_result.status)) @app.route('///mechadmin', methods=['HEAD']) def get_account_mechadmin(account_type, account_id): """Check whether account is mechadmin. Args: account_type (str): GRASS Account Type, 'vendor' or 'subaccount'. account_id (str): GRASS Account Id. Returns: Flask.response: Status code in response header. """ if account_type not in header.ALLOWED_ACCOUNT_TYPES: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) result = mech_admin.is_mech_admin(account_type, account_id) return response.flaskify(response.Response(status=result.status)) @app.route('/vendor//advances', methods=['GET']) def get_vendor_advances(vendor_id): """Get advances for a given vendor. Args: vendor_id (int): unique identifier for vendor. Returns: Flask.response: Status code in response header. """ validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(response=validation) date_start = request.args.get('date_start', None) date_end = request.args.get('date_end', None) filters = dict() if date_start: filters['date_start'] = date_start if date_end: filters['date_end'] = date_end result = advances.get_advances(vendor_id, **filters) return response.flaskify(result) @app.route('/vendor//active', methods=['GET']) def get_active_vendor_contract(vendor_id): """Get active contract for a given vendor. Args: vendor_id (int): unique identifier for vendor. Returns: Flask.response: Status code in response header. """ validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(response=validation) result = vendor_contracts.get_active_vendor_contract(vendor_id) return response.flaskify(result) @app.route('/contracts/', methods=['GET']) def get_vendor_contract(contract_id): """Get contract by contract_id. Args: contract_id (int): unique identifier for contract. Returns: Flask.response: Status code in response header. """ result = vendor_contracts.get_vendor_contract(contract_id) return response.flaskify(result) @app.route('/contracts//royalties', methods=['GET']) def get_vendor_contract_royalties(contract_id): """Get royalty data for a contract. Args: contract_id (int): unique identifier for contract. Returns: Flask.response: Status code in response header. """ result = vendor_contracts.get_contract_royalties(contract_id) return response.flaskify(result) @app.route('/booked/vendor/', methods=['GET']) def get_vendor_booked_contracts(vendor_id): """Get booked contracts for a given vendor. Args: vendor_id (int): unique identifier for vendor. Returns: Flask.response: Response with JSON 'code' and 'message' """ validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(response=validation) sort_order = request.args.get('sort_order', query.DESC) page_limit = int(request.args.get('page_limit', query.PAGE_LIMIT_DEFAULT)) page_offset = int(request.args.get('page_offset', query.PAGE_OFFSET_DEFAULT)) return response.flaskify( vendor_contracts.get_booked_contracts( vendor_id, sort_order, page_limit, page_offset ) ) @app.route('/booked/subaccount/', methods=['GET']) def get_subaccount_booked_contracts(subaccount_id): """Get booked contracts for a given vendor. Args: subaccount_id (int): unique identifier for subaccount. Returns: Flask.response: Response with JSON 'code' and 'message' """ validation = flask_request.verify_grass_access( request, required=False, subaccount=subaccount_id ) if not validation: return response.flaskify(response=validation) vendor_data = account.get_vendor_by_subaccount(subaccount_id) if not vendor_data: return response.flaskify(response=vendor_data) vendor_id = vendor_data.message sort_order = request.args.get('sort_order', query.DESC) page_limit = int(request.args.get('page_limit', query.PAGE_LIMIT_DEFAULT)) page_offset = int(request.args.get('page_offset', query.PAGE_OFFSET_DEFAULT)) return response.flaskify( vendor_contracts.get_booked_contracts( vendor_id, sort_order, page_limit, page_offset ) ) @app.errorhandler(500) def exception_handler(error): """Uncaught exceptions 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 response.flaskify(response.create_fatal_response(message)) @app.route('/mechadmin_for_account', methods=['GET']) def get_account_mech_admin_for_authorized(): """Check whether grass authenticated user is mechadmin. Returns: Flask.response: dict with physical and digital mech admin bool value. """ grass_account_id = request.headers.get(header.GRASS_ACCOUNT_ID) grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) validation = flask_request.verify_grass_access( request, required=True, vendor=grass_account_id, subaccount=grass_account_id ) if not validation: return response.flaskify(validation) result = mech_admin.account_is_mech_admin(grass_account_type, grass_account_id) headers = {'Cache-Control': 'max-age=1800'} return response.flaskify(result, headers) @app.route('///mechadmin_for_account', methods=['GET']) def get_account_mech_admin(account_type, account_id): """Check whether account is digital and physical mechadmin. Args: account_type (str): GRASS Account Type, 'vendor' or 'subaccount'. account_id (str): GRASS Account Id. Returns: Flask.response: dict with physical and digital mech admin bool value. """ if account_type not in header.ALLOWED_ACCOUNT_TYPES: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) result = mech_admin.account_is_mech_admin(account_type, account_id) return response.flaskify(result) @app.route('/contracts/service-types', methods=['GET']) def get_service_types(): """Get all available contract service types. Returns: Flask.response: Status code in response header. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return response.flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Forbidden', status=403, ) ) result = service_types.get_service_types() return response.flaskify(result) @app.route('/contracts//service-type', methods=['GET']) def get_contract_service_type(contract_id): """Get the service type of a given contract. Returns: Flask.response: Status code in response header. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return response.flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Forbidden', status=403, ) ) result = vendor_contracts.get_contract_service_type(contract_id) return response.flaskify(result) @app.route('/contracts/product_split/', methods=['GET']) def get_product_split(vendor_id): """Get the product split for a given upc. Returns: Flask.response: list of UPCs and product_splits """ grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) page_limit = int(request.args.get('page_limit', query.PAGE_LIMIT_DEFAULT)) page_offset = int(request.args.get('page_offset', query.PAGE_OFFSET_DEFAULT)) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) result = vendor_contracts.get_product_split( vendor_id, page_offset=page_offset, page_limit=page_limit ) return response.flaskify(result) @app.route('/contracts/product_split/', methods=['POST', 'PUT']) def set_product_split(vendor_id): """Create the product split for a given upc. Returns: Flask.response: insert status """ grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) data = request.get_json() if request.method == 'POST': result = vendor_contracts.set_product_split(data, vendor_id, grass_account_type) else: result = vendor_contracts.update_product_split( data, vendor_id, grass_account_type ) return response.flaskify(result) @app.route('/contracts/product_split/', methods=['DELETE']) def delete_product_split(upc): """Delete the product split for a given upc. Returns: Flask.response: delete status """ grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) data = request.get_json() if 'vendor_id' not in data: return response.flaskify( response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_VENDOR_ID_MISSING, ) ) validation = flask_request.verify_grass_access( request, required=False, vendor=data['vendor_id'] ) if not validation: return response.flaskify(validation) result = vendor_contracts.delete_product_split(upc, data) return response.flaskify(result) @app.route('/contracts/track_split/', methods=['GET']) def get_track_split(vendor_id): """Get the track split for a given vendor id. Returns: Flask.response: list of isrc and track_splits """ grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) page_limit = int(request.args.get('page_limit', query.PAGE_LIMIT_DEFAULT)) page_offset = int(request.args.get('page_offset', query.PAGE_OFFSET_DEFAULT)) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) result = vendor_contracts.get_track_split( vendor_id, page_limit=page_limit, page_offset=page_offset ) return response.flaskify(result) @app.route('/contracts/track_split/', methods=['POST', 'PUT']) def set_track_split(vendor_id): """Create or update the track split for a given vendor_id. Returns: Flask.response: insert status """ grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) request_params = [ 'vend_contract_id', 'oa_user_id', 'isrc', 'track_unique_id', 'track_split', ] data = request.get_json() for item in data: keys = item.keys() diff = list(set(request_params) - set(keys)) if diff: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_MISSING_REQUEST_PARAMETER.format( key=(', '.join(diff)) ), ) return response.flaskify(error_response) data = request.get_json() if request.method == 'PUT': result = vendor_contracts.update_track_split(vendor_id, data) elif request.method == 'POST': result = vendor_contracts.set_track_split(vendor_id, data) return response.flaskify(result) @app.route('/contracts/track_split/', methods=['DELETE']) def delete_track_split(vendor_id): """Delete the track split for a given vendor_id. Returns: Flask.response: delete status """ grass_account_id = request.headers.get(header.GRASS_ACCOUNT_ID) grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id ) if not validation: return response.flaskify(validation) request_params = ['vend_contract_id', 'oa_user_id', 'isrc'] data = request.get_json() for key in request_params: if key not in data: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_MISSING_REQUEST_PARAMETER.format(key=key), ) return response.flaskify(error_response) result = vendor_contracts.delete_track_split(vendor_id, data) return response.flaskify(result) @app.route('/contracts/product_territory_split/', methods=['GET']) def get_product_territory_split(vendor_id): """Get the product territory split for a given vendor id. Returns: Flask.response: list of product_ids, country_id and split rate """ grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) page_limit = int(request.args.get('page_limit', query.PAGE_LIMIT_DEFAULT)) page_offset = int(request.args.get('page_offset', query.PAGE_OFFSET_DEFAULT)) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) result = vendor_contracts.get_product_territory_split( vendor_id, page_limit=page_limit, page_offset=page_offset ) return response.flaskify(result) @app.route('/contracts/product_territory_split/', methods=['DELETE']) def delete_product_territory_split(upc): """Delete the product territory split for a given UPC and country_id. Returns: Flask.response: delete status """ grass_account_id = request.headers.get(header.GRASS_ACCOUNT_ID) grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id ) if not validation: return response.flaskify(validation) request_params = ['vend_contract_id', 'oa_user_id', 'country_id', 'country_name'] data = request.get_json() for key in request_params: if key not in data: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_MISSING_REQUEST_PARAMETER.format(key=key), ) return response.flaskify(error_response) result = vendor_contracts.delete_product_territory_split(upc, data) return response.flaskify(result) @app.route('/contracts/product_territory_split/', methods=['POST', 'PUT']) def set_product_territory_split(vendor_id): """Create or update the product territory split for a given vendor_id. Returns: Flask.response: insert status """ grass_account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_INVALID_GRASS_ACCOUNT_TYPE, ) return response.flaskify(error_response) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) request_params = [ 'vend_contract_id', 'oa_user_id', 'upc', 'country_id', 'split_rate', 'country_name', ] data = request.get_json() for item in data: keys = item.keys() diff = list(set(request_params) - set(keys)) if diff: error_response = response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_MISSING_REQUEST_PARAMETER.format( key=(', '.join(diff)) ), ) return response.flaskify(error_response) if request.method == 'PUT': return response.flaskify( vendor_contracts.update_product_territory_split( data, vendor_id, grass_account_type ) ) elif request.method == 'POST': return response.flaskify( vendor_contracts.set_product_territory_split( data, vendor_id, grass_account_type ) ) @app.route('/contracts/vendor-currency', methods=['GET']) def get_vendor_currency(): """Get the currency for a given vendor. Returns: Flask.response: Response with JSON 'code' and 'message' """ vendor_id = request.headers.get(header.GRASS_ACCOUNT_ID) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) return response.flaskify(vendor_contracts.get_vendor_currency(vendor_id)) @app.route( '/booked/vendor//first-period//' 'last-period/', methods=['GET'], ) def get_vendor_booked_contracts_by_period_range( vendor_id, first_period_id, last_period_id ): """Get booked contracts for a given vendor. Args: vendor_id (int): unique identifier for vendor. first_period_id (int): the first period id of the range last_period_id (int): the last period id of the range Returns: Flask.response: Response with JSON 'code' and 'message' """ validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(response=validation) sort_order = request.args.get('sort_order', query.DESC) return response.flaskify( vendor_contracts.get_booked_contracts_by_period_range( vendor_id, first_period_id, last_period_id, sort_order ) ) @app.route('/subaccount//royalty_collection', methods=['GET']) def get_subaccount_royalty_collection(subaccount_id): """Get royalty collection for a given subaccount. Args: subaccount_id (int): unique identifier for subaccount. Returns: Flask.response: Response with subaccount royalty collection entity """ validation = flask_request.verify_grass_access( request, required=False, subaccount=subaccount_id ) if not validation: return response.flaskify(response=validation) return response.flaskify( subaccount_royalty_collection.get_active_subaccount_royalty_collection( subaccount_id ) ) @app.route( '/subaccount/royalty_collection//' 'royalty_collection_territories', methods=['GET'], ) def get_subaccount_royalty_collection_territories(subaccount_royalty_collection_id): """Get royalty collection territories for a given subaccount royalty collection. Args: subaccount_royalty_collection_id (int): unique identifier for subaccount royalty collection. Returns: Flask.response: Response with subaccount royalty collection territories entity """ subaccount_id = ( subaccount_royalty_collection.get_subaccount_id_by_royalty_collection_id( subaccount_royalty_collection_id ).message ) validation = flask_request.verify_grass_access( request, required=False, subaccount=subaccount_id ) if not validation: return response.flaskify(response=validation) return response.flaskify( subaccount_royalty_collection.get_subaccount_royalty_collection_territories( subaccount_royalty_collection_id ) ) @app.route('/contracts/service_type/', methods=['PATCH']) def update_vendor_contract_service_type_id(vendor_id): """Update the service type ID for a vendor contract. Args: vendor_id (str): The ID of the vendor. Returns: Response: The Flask response object. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return response.flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Forbidden', status=403, ) ) validation = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation: return response.flaskify(validation) data = request.get_json() service_type_id = data.get('service_type_id') result = vendor_contracts.update_service_type_by_vendor_id( vendor_id, service_type_id ) if not result: return response.flaskify(result) return response.flaskify(response.Response(result)) @app.route( '/subaccount/subaccount_royalty_collection/', methods=['GET'], ) def get_royalty_collection(subaccount_royalty_collection_id): """Get get royalty collection by a provided royalty collection id. Args: subaccount_royalty_collection_id (int): unique identifier for subaccount royalty collection. Returns: Flask.response: Response with subaccount id """ subaccount = ( subaccount_royalty_collection.get_subaccount_id_by_royalty_collection_id( subaccount_royalty_collection_id ) ) if not subaccount or not subaccount.message: return response.flaskify(subaccount) subaccount_id = subaccount.message validation = flask_request.verify_grass_access( request, required=False, subaccount=subaccount_id ) if not validation: return response.flaskify(response=validation) return response.flaskify( subaccount_royalty_collection.get_royalty_collection( subaccount_royalty_collection_id ) )