"""Blueprint for contract API.""" from abacus_common_logic.utils.authorization import permissions_authorize_many_accounts from abacus_common_logic.views.create_view import CreateView from abacus_common_logic.views.item_view import ItemView from abacus_common_logic.views.list_view import ListView from abacus_common_logic.views.validations import validated_request_body from flask import Blueprint from flask import g from flask import request from flask import Response from flask import stream_with_context from owsrequest import flask_request from owsresponse import response from owsresponse.adaptors.flask import flaskify from abacus_contract.config import ows_client from abacus_contract.constants.error import \ ERROR_CODE_AUTHORIZATION, \ ERROR_CODE_FORBIDDEN, \ ERROR_INVALID_BODY, \ ERROR_INVALID_IDS, \ ERROR_MESSAGE_FORBIDDEN_USER from abacus_contract.logic import contract as logic from abacus_contract.logic.contract_search import get_contracts from abacus_contract.models.contract import Contract from abacus_contract.schemas.contract import \ ContractAndLifecyclePostSchema, ContractTerminationSchema from abacus_contract.schemas.contract import ContractDetailSchema from abacus_contract.schemas.contract import ContractPostSchema from abacus_contract.schemas.contract import ContractPutSchema from abacus_contract.schemas.contract import ContractSapFormattedSchema from abacus_contract.utils.authorization import pdp_authorize_many_accounts from abacus_contract.utils.format_error import validation_error from abacus_contract.utils.request import get_optional_numeric_list_from_params contract_api = Blueprint('contract_api', __name__) class ContractList(ListView): """View for listing contracts.""" model_class = Contract list_entry_schema = ContractDetailSchema() def get(self): """GET contracts.""" contracts = get_contracts(request.args) access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: account_ids = [item['account_id'] for item in contracts.message['items']] if account_ids: authorized = pdp_authorize_many_accounts(account_ids) if not authorized: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403, )) return flaskify(contracts) class ContractItemView(ItemView): """View for finding a contract by ID.""" model_class = Contract put_schema = ContractPutSchema() object_detail_schema = ContractDetailSchema() def get(self, object_id, **kwargs): """Get a contract by ID.""" contract_response = super().get(object_id, **kwargs) account_id = contract_response.get_json().get('account_id') access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: authorized = pdp_authorize_many_accounts(account_ids=[account_id]) if not authorized: return flaskify( response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403, ) ) account_access = permissions_authorize_many_accounts( ows_client, g.request_context.profile_type, g.request_context.profile_id, [account_id] ) if not account_access: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403 )) return contract_response def put(self, object_id, **kwargs): """Update a contract by ID.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) return super().put(object_id, **kwargs) def update_handler(self, obj, **params): """Handle contract updates.""" return logic.update_contract(obj, **params) class ContractSapItemView(ItemView): """View for formatting a contract info for SAP api.""" model_class = Contract object_detail_schema = ContractSapFormattedSchema() def get(self, object_id, **kwargs): """Get contract info for SAP.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) return flaskify(logic.sap_details(object_id)) class ContractCreateView(CreateView): """Handles contract creation.""" post_schema = ContractPostSchema() def post(self, **kwargs): """Create a contract.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) return super().post(**kwargs) def create_handler(self, **params): """Create a contract.""" return logic.create_contract(**params) contract_api.add_url_rule( '/contracts/', methods=['GET'], view_func=ContractList.as_view('list_contracts') ) contract_api.add_url_rule( '/contract//sap', methods=['GET'], view_func=ContractSapItemView.as_view('contract_sap') ) contract_api.add_url_rule( '/contract/', methods=['GET', 'PUT'], view_func=ContractItemView.as_view('contract') ) contract_api.add_url_rule( '/contract', view_func=ContractCreateView.as_view('create_contract') ) @contract_api.route('/contracts/', methods=['POST']) def get_contracts_by_ids(): """Get contracts by list of contract_ids. NOTE: This endpoint uses a POST method to allow for a larger list of query args. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) try: contract_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify(validation_error(ERROR_INVALID_IDS.format(object='Contract'))) contracts = logic.get_contracts_by_ids(contract_ids) return flaskify(response.Response(message=contracts, status=200)) @contract_api.route('/contracts/dataloader', methods=['POST']) def get_contracts_dataloaded(): """Get contracts by a list of contract_ids. Note: This endpoint uses a POST method to allow for a list of ids to be passed in the body of the request. """ try: contract_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify( validation_error( ERROR_INVALID_IDS.format(object='Contract') ) ) contracts = logic.get_contracts_by_ids(contract_ids) account_ids = [contract.get('account_id') for contract in contracts] access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: authorized = pdp_authorize_many_accounts(account_ids) if not authorized: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403, )) authorized = permissions_authorize_many_accounts( ows_client, g.request_context.profile_type, g.request_context.profile_id, account_ids ) if not authorized: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403, )) return flaskify(logic.format_contracts_for_dataloader(contract_ids, contracts)) @contract_api.route('/contracts/account/', methods=['GET']) def get_contracts_by_account_handler(account_id): """Handle getting contracts associated to the specified account.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) account_access = permissions_authorize_many_accounts( ows_client, g.request_context.profile_type, g.request_context.profile_id, [account_id] ) if not account_access: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403 )) return flaskify(logic.get_contracts_by_account([account_id])) @contract_api.route( '/contracts/account/dataloader', methods=['POST'] ) def get_contracts_by_account_dataloader_handler(): """Handle getting contracts associated to the specified dataloaded accounts. NOTE: This endpoint uses a POST method to allow for a larger list of query args. """ try: account_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify( validation_error( ERROR_INVALID_IDS.format(object='AccountId') ) ) access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: authorized = pdp_authorize_many_accounts(account_ids) if not authorized: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403, )) authorized = permissions_authorize_many_accounts( ows_client, g.request_context.profile_type, g.request_context.profile_id, account_ids ) if not authorized: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403 )) return flaskify(logic.get_contracts_by_account(account_ids, dataload=True)) @contract_api.route('/contracts/accounts', methods=['POST']) def get_contracts_by_accounts(): """Get contracts by list of account_ids. NOTE: This endpoint uses a POST method to allow for a larger list of query args. """ try: account_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify(validation_error(ERROR_INVALID_IDS.format(object='Account'))) access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: authorized = pdp_authorize_many_accounts(account_ids) if not authorized: return flaskify(response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403, )) return flaskify(logic.get_contracts_by_account(account_ids)) @contract_api.route('/contracts/snapshot', methods=['POST']) def get_tsv_contract_snapshot(): """Get contracts as a TSV snapshot. NOTE: This endpoint uses a POST method to allow for a larger list of query args. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) try: optional_contract_ids = get_optional_numeric_list_from_params(optional=True) except ValueError: return flaskify(validation_error(ERROR_INVALID_IDS.format(object='Contract'))) return Response( stream_with_context(logic.contract_export(optional_contract_ids)), mimetype='text/tsv' ) @contract_api.route('/contracts/vat-info', methods=['POST']) def get_contracts_vat_info(): """Get contracts vat info by list of contract_ids. Used by lambda-abacus-vat-calculate. NOTE: This endpoint uses a POST method to allow for a larger list of query args. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) try: contract_ids = get_optional_numeric_list_from_params(optional=True) except ValueError: return flaskify(validation_error(ERROR_INVALID_IDS.format(object='Contract'))) if not contract_ids: return flaskify(validation_error(ERROR_INVALID_BODY.format( expected_body_type='List of Contract IDs' ))) return flaskify(logic.get_vat_info_by_contract_ids(contract_ids)) @contract_api.route( '/contract/contract-lifecycle-schedule/contract-lifecycle', methods=['POST'] ) def create_contract_with_lifecycle_and_schedules(): """Create a contract with contract lifecycle and schedules.""" post_request_body = validated_request_body( ContractAndLifecyclePostSchema() ) access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) return flaskify( logic.create_contract_with_lifecycle_and_schedules(**post_request_body) ) @contract_api.route( '/contract//terminate', methods=['PUT'] ) def terminate_contract(contract_id): """Terminate a contract.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) termination_request_body = validated_request_body(ContractTerminationSchema()) return flaskify(logic.terminate_contract(contract_id, **termination_request_body)) @contract_api.route( '/contract//reactivate', methods=['PUT'] ) def reactivate_contract(contract_id: int): """Reactivate a contract by id. Args: contract_id (int): id of the contract """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) return flaskify(logic.reactivate_contract(contract_id)) @contract_api.route( '/contract//can-be-deleted', methods=['GET'] ) def can_contract_be_deleted(contract_id: int): """Check if a contract can be deleted.""" account_id = Contract.get_by_id_or_error(contract_id).account_id access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: authorized = pdp_authorize_many_accounts(account_ids=[account_id]) if not authorized: return flaskify( response.create_error_response( code=ERROR_CODE_FORBIDDEN, message=ERROR_MESSAGE_FORBIDDEN_USER, status=403, ) ) return flaskify(logic.can_contract_be_deleted(contract_id)) @contract_api.route( '/contract/', methods=['DELETE'] ) def delete_contract(contract_id: int): """Delete a contract.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, )) return flaskify(logic.delete_contract(contract_id))