"""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 time from flask import g from flask import jsonify from flask import request from oto import response from oto.adaptors.flask import flaskify from owsrequest import flask_request from pricing import config from pricing.api import app from pricing.api import cache from pricing.constants import error, header from pricing.logic import bulk_product_update, orchard_pricing_tier_value from pricing.logic import default_account_pricing_tier from pricing.logic import orchard_pricing_tier from pricing.logic import pricing_copy as pricing_copy_logic from pricing.logic import pricing_family from pricing.logic import pricing_helper from pricing.logic import product from pricing.logic import product_orchard_pricing_tier from pricing.logic import product_pricing_override from pricing.logic import product_store_effective_price_code from pricing.logic import product_store_pricing from pricing.logic import store_pricing_scheme from pricing.logic import store_pricing_tier from pricing.logic import store_pricing_tier_code from pricing.logic import track from pricing.logic import track_pricing_override from pricing.logic import product_pricing_validator @app.route('/pricing-families', methods=['GET']) def get_pricing_families(): """Return a list of all pricing families.""" return flaskify(pricing_family.get_pricing_families()) @app.route('/pricing-family//orchard-pricing-tier', methods=['GET']) def get_orchard_pricing_tier_by_pricing_family_id(pricing_family_id): """Return a list of Orchard Pricing Tiers by pricing family ID. Args: pricing_family_id (int): the ID of the pricing family. Returns: flask.Response: containing the orchard pricing tiers as dict. """ headers = {'Cache-Control': 'max-age=3600'} return flaskify(orchard_pricing_tier. get_orchard_pricing_tier_by_pricing_family_id( pricing_family_id), headers) @app.route('/public/pricing-family//orchard-pricing-tier', methods=['GET']) def get_public_orchard_pricing_tier_by_pricing_family_id(pricing_family_id): """Return a list of Orchard Pricing Tiers by pricing family ID. Args: pricing_family_id (int): the ID of the pricing family. Returns: flask.Response: containing the orchard pricing tiers as dict. """ headers = {'Cache-Control': 'max-age=3600'} return flaskify(orchard_pricing_tier. get_orchard_pricing_tier_by_pricing_family_id( pricing_family_id), headers) @app.route('/pricing-family//store-pricing-scheme', methods=['GET']) def get_store_pricing_scheme_by_pricing_family_id(pricing_family_id): """Return a list of Store Pricing Schemes by pricing family ID. Args: pricing_family_id (int): the ID of the pricing family. Returns: flask.Response: containing the store pricing schemes as dict. """ return flaskify(store_pricing_scheme.get_by_pricing_family_id( pricing_family_id)) @app.route('/admin/pricing-family', methods=['POST']) def create_pricing_family(): """Create a pricing family. Returns: flask.Response: containing the created pricing family as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(pricing_family.create_pricing_family(data)) @app.route('/admin/orchard-pricing-tier', methods=['POST']) def create_orchard_pricing_tier(): """Create orchard pricing tier. Returns: flask.Response: containing the created orchard pricing tier as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(orchard_pricing_tier.create_orchard_pricing_tier(data)) @app.route('/admin/orchard-pricing-tier/', methods=['PUT']) def update_orchard_pricing_tier(orchard_pricing_tier_id): """Update orchard pricing tier. Returns: flask.Response: containing the updated orchard pricing tier as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(orchard_pricing_tier.update_orchard_pricing_tier_by_id( orchard_pricing_tier_id, data)) @app.route('/store-pricing-scheme/', methods=['GET']) def get_store_pricing_scheme_by_id(store_pricing_scheme_id): """Get store pricing scheme by ID. Returns: flask.Response: containing the store pricing scheme as dict. """ return flaskify(store_pricing_scheme.get_store_pricing_scheme_by_id( store_pricing_scheme_id)) @app.route('/store//store-pricing-scheme', methods=['GET']) def get_store_pricing_scheme_by_store_id(store_id): """Get a list of store pricing schemes by store ID. Returns: flask.Response: containing the list of store pricing schemes as dict. """ return flaskify(store_pricing_scheme.get_by_store_id(store_id)) @app.route('/admin/store-pricing-scheme', methods=['POST']) def create_store_pricing_scheme(): """Create store pricing scheme. Returns: flask.Response: containing the created store pricing scheme as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(store_pricing_scheme.create_store_pricing_scheme(data)) @app.route( '/admin/store-pricing-scheme/', methods=['PUT']) def update_store_pricing_scheme(store_pricing_scheme_id): """Update store pricing scheme. Args: store_pricing_scheme_id (int): the ID of the store pricing scheme. Returns: flask.Response: containing the updated store pricing scheme as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(store_pricing_scheme.update_store_pricing_scheme_by_id( store_pricing_scheme_id, data)) @app.route( '/admin/store-pricing-scheme//store-pricing-tier', methods=['POST']) def create_store_pricing_tier(store_pricing_scheme_id): """Create store pricing pricing tier. Args: store_pricing_scheme_id (int): the ID of the scheme to connect to. Returns: flask.Response: containing the created store pricing tier as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(store_pricing_tier.create_store_pricing_tier( store_pricing_scheme_id, data)) @app.route( '/admin/store-pricing-scheme/store-pricing-tier/', methods=['PUT']) def update_store_pricing_tier(store_pricing_tier_id): """Update store pricing pricing tier. Args: store_pricing_tier_id (int): the ID of the scheme to connect to. Returns: flask.Response: containing the created store pricing tier as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(store_pricing_tier.update_store_pricing_tier_by_id( store_pricing_tier_id, data)) @app.route( '/admin/store-pricing-scheme/store-pricing-tier/', methods=['DELETE']) def delete_store_pricing_tier(store_pricing_tier_id): """Delete a store pricing pricing tier. Args: store_pricing_tier_id (int): the ID of the tier to delete. Returns: flask.Response: containing the delete store pricing tier as dict. """ return flaskify(store_pricing_tier.delete(store_pricing_tier_id)) @app.route('/admin/store-pricing-tier//' 'store-pricing-tier-code', methods=['POST']) def create_store_pricing_tier_code(store_pricing_tier_id): """Create store pricing tier code. Args: store_pricing_tier_id (int): the ID of the tier to connect to. Returns: flask.Response: containing the created store pricing tier code as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(store_pricing_tier_code.create( store_pricing_tier_id, data)) @app.route( '/admin/store-pricing-tier//' 'store-pricing-tier-code/', methods=['PUT']) def update_store_pricing_tier_code( store_pricing_tier_id, store_pricing_tier_code_id): """Update store pricing tier code. Args: store_pricing_tier_id (int): the ID of the tier to connect to. store_pricing_tier_code_id (int): the ID of the tier code. Returns: flask.Response: containing the updated store pricing tier code as dict. """ data = request.get_json(silent=True, force=True) or {} return flaskify(store_pricing_tier_code.update( store_pricing_tier_id, store_pricing_tier_code_id, data)) @app.route('/admin/store_pricing_tier_code/', methods=['DELETE']) def delete_store_pricing_tier_code(store_pricing_tier_code_id): """Delete a store pricing tier code. Args: store_pricing_tier_code_id (int): the Id of the store pricing tier code Returns: flask.Response: containint the deleted store pricing tier code """ return flaskify(store_pricing_tier_code.delete(store_pricing_tier_code_id)) @app.route( '/product//pricing-family//' 'orchard_pricing_tier', methods=['GET']) def get_product_orchard_pricing_tier(product_id, pricing_family_id): """Get product orchard pricing tier. Args: product_id (int): the ID of the product. pricing_family_id (int): the ID of the pricing family. Returns: flask.Response: containing the product orchard pricing tier as dict. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) return flaskify( product_orchard_pricing_tier.get_by_product_id_and_pricing_family_id( product_id, pricing_family_id)) @app.route( '/product//pricing-family//' 'orchard_pricing_tier', methods=['PUT']) def update_product_with_orchard_pricing_tier(product_id, pricing_family_id): """Update product orchard pricing tier. Args: product_id (int): the ID of the product to set orchard pricing tier on. pricing_family_id (int): the ID of the pricing family. Returns: flask.Response: containing the product orchard pricing tier as dict. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify( product_orchard_pricing_tier.update_product_with_orchard_pricing_tier( product_id, pricing_family_id, data)) @app.route('/product//override', methods=['GET']) def get_product_pricing_overrides(product_id): """Get the product pricing overrides. Args: product_id (int): the ID of the product to get overrides for. Returns: flask.Response: containing the product pricing overrides as dicts. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) return flaskify(product_pricing_override.get_by_product_id(product_id)) @app.route('/product//override', methods=['POST']) def create_product_pricing_override(product_id): """Create a new product pricing override. Args: product_id (int): the ID of the product to create override for. Returns: flask.Response: containing the new created pricing override. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify(product_pricing_override. create_product_pricing_override_with_change_detection( product_id, data)) @app.route('/product//override/bulk', methods=['POST']) def bulk_create_product_pricing_override(product_id): """Create new product pricing overrides. Args: product_id (int): the ID of the product to create overrides for. Returns: flask.Response: containing the new created pricing overrides. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify(product_pricing_override. bulk_create_product_pricing_override(product_id, data)) @app.route('/product//override/bulk', methods=['PUT']) def bulk_update_create_product_pricing_override(product_id): """Update and create product pricing overrides. Args: product_id (int): the ID of the product to update overrides for. Returns: flask.Response: containing the new created pricing overrides. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify( product_pricing_override.bulk_update_create_product_pricing_override( product_id, data)) @app.route('/product//track-override/bulk', methods=['POST']) def bulk_create_track_pricing_override(product_id): """Create new track pricing overrides. Args: product_id (int): the ID of the product to create overrides for. Returns: flask.Response: containing the new created track pricing overrides. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify(track_pricing_override. bulk_create_track_pricing_override(product_id, data)) @app.route('/product//track-override/bulk', methods=['PUT']) def bulk_update_create_track_pricing_override(product_id): """Update and create track pricing overrides. Args: product_id (int): the ID of the product to update overrides for. Returns: flask.Response: containing the new created pricing overrides. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify( track_pricing_override.bulk_update_create_track_pricing_override( product_id, data)) @app.route('/product/bulk-update', methods=['POST']) def bulk_update_products(): """Bulk update products. Returns: flask.Response: containing the created overrides and tiers. """ data = request.get_json(silent=True, force=True) or {} return flaskify(bulk_product_update.bulk_update_products(data)) @app.route( '/product//override/', methods=['PUT']) def update_product_pricing_override(product_id, product_pricing_override_id): """Update a product pricing override. Args: product_id (int): the ID of the product to update pricing override. product_pricing_override_id (int): the ID of the pricing override. Returns: flask.Response: containing the updated pricing override. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify(product_pricing_override.update_product_pricing_override( product_id, product_pricing_override_id, data)) @app.route('/pricing_family//product//' 'store//pricing', methods=['GET']) def get_product_pricing_by_store_id(pricing_family_id, product_id, store_id): """Get the fully mapped product pricing for a particular store. Args: pricing_family_id (int): the ID of the pricing family. product_id (int): the ID of the product. store_id (int): the ID of the store. Returns: flask.Response: containing the product pricing dict. """ if config.ENVIRONMENT != config.TEST_ENVIRONMENT: start_time = time.time() ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) result = product_store_pricing.get_product_store_pricing( pricing_family_id, product_id, store_id) if config.ENVIRONMENT != config.TEST_ENVIRONMENT \ and result.message and 'performance_profile' in result.message: result.message['performance_profile']['request-duration'] = \ time.time() - start_time return flaskify(result) @app.route('/track//override', methods=['GET']) def get_track_pricing_overrides(track_id): """Get the track pricing overrides. Args: track_id (int): the ID of the product to get overrides for. Returns: flask.Response: containing the product pricing overrides as dicts. """ track_response = track.get_track_by_track_id(track_id) if not track_response: return flaskify(track_response) ownership = flask_request.verify_grass_ownership( request, product.is_owner, track_response.message['product_id']) if not ownership: return flaskify(ownership) return flaskify(track_pricing_override.get_by_track_id(track_id)) @app.route('/product//track-overrides', methods=['GET']) def get_track_pricing_overrides_for_product(product_id): """Get all the track pricing overrides for a product. Args: product_id (int): the ID of the product to get overrides for. Returns: flask.Response: containing the track pricing overrides as dicts. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) return flaskify(track_pricing_override .get_track_pricing_overrides_for_product(product_id)) @app.route('/track//override', methods=['POST']) def create_track_pricing_override(track_id): """Create a new track pricing override. Args: track_id (int): the ID of the track to create override for. Returns: flask.Response: containing the new created track override. """ track_response = track.get_track_by_track_id(track_id) if not track_response: return flaskify(track_response) ownership = flask_request.verify_grass_ownership( request, product.is_owner, track_response.message['product_id']) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify(track_pricing_override.create_track_pricing_override( track_id, data)) @app.route('/track//override/', methods=['PUT']) def update_track_pricing_override(track_id, track_pricing_override_id): """Update a track pricing override. Args: track_id (int): the ID of the track to update override for. track_pricing_override_id (int): the ID of the override. Returns: flask.Response: containing the updated track override. """ track_response = track.get_track_by_track_id(track_id) if not track_response: return flaskify(track_response) ownership = flask_request.verify_grass_ownership( request, product.is_owner, track_response.message['product_id']) if not ownership: return flaskify(ownership) data = request.get_json(silent=True, force=True) or {} return flaskify(track_pricing_override.update_track_pricing_override( track_id, track_pricing_override_id, data)) @app.route( '/product//pricing/copy/', methods=['POST']) def copy_digital_product_pricing( source_product_id, destination_product_id): """Copy pricing for newly created digital product. Args: source_product_id (int): the product_id of the product to copy from. destination_product_id (int): the product_id of the product to copy to. Returns: flask.Response: 200 status on success. """ result = pricing_copy_logic.copy_pricing_for_digital_product( source_product_id=source_product_id, destination_product_id=destination_product_id) return flaskify(result) @app.route('////default', methods=['GET']) def get_default_account_pricing_tier( account_type, account_id, pricing_family_id): """Get default pricing tier. Args: account_type (string): indicates if vendor or subaccount. account_id (int): indicates if vendor_id or subaccount_id. pricing_family_id (int): the ID of the pricing family. Returns: flask.Response: containing the default account pricing tier as dict. """ headers_response = flask_request.verify_grass_headers(request) if not headers_response: return flaskify(headers_response) return flaskify( default_account_pricing_tier.get_default_account_pricing_tier( account_type, account_id, pricing_family_id)) @app.route( '////default', methods=['PUT']) def set_default_account_pricing_tier( account_type, account_id, pricing_family_id): """Set default account pricing tier. Args: account_type (string): indicate whether it is a vendor or subaccount. account_id (int): either vendor_id or subaccount_id. pricing_family_id (int): pricing family id for a product. Returns: flask.Response: Returns inserted default pricing tier details. """ headers_response = flask_request.verify_grass_headers(request) if not headers_response: return flaskify(headers_response) data = request.get_json(silent=True, force=True) or {} if not data: return flaskify(response.Response(status=400)) data['created_by'] = request.headers.get(header.GRASS_ORCHARD_USER_ID, '') return flaskify( default_account_pricing_tier.set_default_account_pricing_tier( account_type, account_id, pricing_family_id, data)) @app.route( '////default', methods=['DELETE']) def soft_delete_default_account_pricing_tier( account_type, account_id, pricing_family_id): """Soft delete default account pricing tier. Args: account_type (string): indicate whether it is a vendor or subaccount. account_id (int): either vendor_id or subaccount_id. pricing_family_id (int): pricing family id for a product. Returns: flask.Response: Returns soft deleted default pricing tier details. """ headers_response = flask_request.verify_grass_headers(request) if not headers_response: return flaskify(headers_response) data = { 'created_by': request.headers.get(header.GRASS_ORCHARD_USER_ID, '')} return flaskify( default_account_pricing_tier.soft_delete_default_account_pricing_tier( account_type, account_id, pricing_family_id, data)) @app.route('/clear-cache', methods=['GET']) def clear_cache(): """Clear cache. Returns: flask.Response: Success response """ cache.clear() return flaskify(response.Response()) @app.route('/product//effective-price-codes', methods=['GET']) def get_product_effective_price_codes(product_id): """Get the effective pricing across multiple stores for a product. Args: product_id (int): the ID of the product. Returns: flask.Response: containing the product pricing dict. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) result = product_store_effective_price_code.get_by_product_id(product_id) return flaskify(result) @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): """Handle case 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('/upload-token', methods=['GET']) def get_upload_token(): """Generate data required for pricing csv upload. Returns: flask.Response: Response containing upload data or error. """ user_id = request.headers.get(header.GRASS_ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Failed to identify user', status=401 )) duration = int(request.args.get( config.DURATION, config.STS_TOKEN_DURATION)) if duration < config.MIN_STS_TOKEN_DURATION: duration = config.MIN_STS_TOKEN_DURATION return flaskify(pricing_helper.get_upload_permission( user_id, duration)) @app.route('/upload/presigned-urls', methods=['GET']) def get_upload_presigned_urls(): """Generate presigned urls required for pricing csv upload. Returns: flask.Response: Response containing upload data or error. """ user_id = request.headers.get(header.GRASS_ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Failed to identify user', status=401 )) parts = int(request.args.get('parts', 1)) result = pricing_helper.get_presigned_urls(parts) return flaskify(result) @app.route('/upload/complete', methods=['PUT']) def complete_multipart_upload(): """Complete upload for pricing csv upload. Returns: flask.Response: Response containing success or error. """ user_id = request.headers.get(header.GRASS_ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Failed to identify user', status=401 )) data = request.get_json(silent=True) if not data or 'filename' not in data or 'upload_token' not in data or 'parts' not in data: return flaskify(response.create_error_response( code=error.ERROR_CODE_BAD_GRASS_REQUEST, message='Incomplete body', status=400 )) result = pricing_helper.complete_multipart_upload(data) return flaskify(result) @app.route('/product//digital/bff', methods=['GET']) def get_all_digital_product_pricing(product_id): """Get all relevent pricing information for the digital frontend. Args: product_id (int): the ID of the product. Returns: flask.Response: The product pricing info as a dict. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) return flaskify(pricing_helper.get_all_digital_product_pricing(product_id)) @app.route('/product//validate', methods=['GET']) def validate_product_pricing(product_id): """Validate a product's pricing Args: product_id (int): the ID of the product. Returns: flask.Response: validation warnings or success. """ ownership = flask_request.verify_grass_ownership( request, product.is_owner, product_id) if not ownership: return flaskify(ownership) return flaskify( product_pricing_validator.validate_product_by_id( product_id)) @app.route('/orchard-pricing-tier-values', methods=['GET']) def get_pricing_tier_values(): """Return a list of all pricing tier values.""" return flaskify(orchard_pricing_tier_value.get_all_pricing_tier_values())