"""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: ows_product_physical.response for more details. """ from flask import g from flask import request from oto import response from oto.adaptors.flask import flaskify from ows_product_physical.api import app from ows_product_physical.constant import header from ows_product_physical.logic import orders from ows_product_physical.logic import product_delivery from ows_product_physical.logic import product_distribution from ows_product_physical.logic import product_physical from ows_product_physical.logic import product_physical_change_history from ows_product_physical.logic import product_physical_packaging from ows_product_physical.logic import product_physical_supply_chain_metadata from ows_product_physical.logic import product_physical_tracks from ows_product_physical.schemas.handlers import \ create_order, get_orders, update_order from ows_product_physical.schemas.handlers.get_physical_product_change_history\ import GetProductPhysicalChangeHistorySchema from ows_product_physical.schemas.handlers.products_for_new_delivery import \ ProductsForNewDeliverySchema from ows_product_physical.utils import handler_utils from ows_product_physical.validation import request_context from ows_product_physical.validation import validation @app.route('/hello') def health(): """Check the health of the application. Returns: flask.Response: result of creating a physical product. """ return flaskify(product_physical.healthcheck()) @app.route('/packaging', methods=['GET']) @validation.raml_validate def get_product_physical_packaging(): """Return a list of all physical packaging options. Returns: flask.Response: result of fetching packaging options. """ resp = product_physical_packaging.get() return flaskify(resp) @app.route('/products//new-delivery', methods=['GET']) @handler_utils.parse_and_validate_request_json(ProductsForNewDeliverySchema) def eligible_products_for_new_delivery(store_id, json_data): """Fetch list of eligible product_id for this store_id. Args: store_id (int): store id (mandatory) Params: json_data (dict): dict of params parsed by ProductsForNewDeliverySchema Returns: flask.Response: JSON object representing the physical product. """ request_context_valid = request_context.validate_request_context( g.request_context, role=header.DELIVER_PHYSICAL_AUDIO_ROLE) if not request_context_valid: return flaskify(request_context_valid) result = product_delivery.eligible_products_for_new_delivery( **json_data) return flaskify(result) @app.route('/products//takedown-delivery', methods=['GET']) @handler_utils.parse_and_validate_request_json(ProductsForNewDeliverySchema) def deleted_products_for_takedown(store_id, json_data): """Fetch list of deleted product_id for this store_id. Args: store_id (int): store id (mandatory) Params: json_data (dict): dict of params parsed by ProductsForNewDeliverySchema Returns: flask.Response: JSON object representing the physical product. """ request_context_valid = request_context.validate_request_context( g.request_context, role=header.DELIVER_PHYSICAL_AUDIO_ROLE) if not request_context_valid: return flaskify(request_context_valid) result = product_delivery.deleted_products_for_takedown( **json_data) return flaskify(result) @app.route('/products//update-delivery', methods=['GET']) @handler_utils.parse_and_validate_request_json(ProductsForNewDeliverySchema) def products_eligible_for_update_delivery(store_id, json_data): """Fetch list of eligible product_ids for this store_id. Args: store_id (int): store id (mandatory) Params: json_data (dict): dict of params parsed by ProductsForNewDeliverySchema Returns: flask.Response: JSON object representing the physical product. """ request_context_valid = request_context.validate_request_context( g.request_context, role=header.DELIVER_PHYSICAL_AUDIO_ROLE) if not request_context_valid: return flaskify(request_context_valid) result = product_delivery.products_eligible_for_update_delivery( **json_data) return flaskify(result) @app.route('/product/', methods=['GET']) @validation.raml_validate def product_physical_fetch(product_id): """Fetch an existing product by it's primary key, product_id. Returns: flask.Response: JSON object representing the physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_physical.fetch_by_id(product_id, account_type, account_id) return flaskify(result) @app.route('/product', methods=['POST']) @validation.raml_validate def product_physical_create(): """Create a new product, validate inputs. Returns: flask.Response: result of creating a physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) data = request.get_json() result = product_physical.create(data, account_type, account_id) return flaskify(result) @app.route('/product/', methods=['PUT']) @validation.raml_validate def product_physical_update(product_id): """Update an existing product. Returns: flask.Response: JSON object representing the physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) data = request.get_json() result = product_physical.update( product_id, data, account_type, account_id) return flaskify(result) @app.route('/product//copy', methods=['POST']) @validation.reject_grass_headers @validation.raml_validate def copy_product(product_id): """Copy a product. Args: product_id (int): The product_id (release_id) of the product to copy. Returns: flask.response: The product_id of the new product. """ data = request.get_json() copy_product_response = product_physical.copy_product(product_id, data) return flaskify(copy_product_response) @app.route( '/product//tracks/copy/', methods=['POST']) @validation.reject_grass_headers @validation.raml_validate def copy_product_tracks(product_id, new_product_id): """Copy product tracks to another product. Args: product_id (int): The product_id of the product to copy from. new_product_id (int): The product_id of the product to copy to. Returns: flask.response: empty 201 response if successful, or error response. """ return flaskify( product_physical_tracks.copy_product_tracks( product_id, new_product_id)) @app.route('/product/', methods=['DELETE']) @validation.raml_validate def product_physical_delete(product_id): """Delete an existing product. Returns: flask.Response: Includes a response code of the operation """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_physical.delete(product_id, account_type, account_id) return flaskify(result) @app.route('/product//status', methods=['PUT']) @validation.raml_validate def product_physical_update_status(product_id): """Update the status of a product in the approval flow. Returns: flask.Response: Includes response code of the operation. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) data = request.get_json() result = product_physical.update_product_status( product_id, data.get('status'), account_type, account_id) return flaskify(result) @app.route('/product//tracks', methods=['GET']) @validation.raml_validate def product_physical_get_tracks(product_id): """Get tracks for a product. Returns ------- flask.Response: JSON array representing the new list of tracks. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_physical_tracks.get_tracks( product_id, account_type, account_id) return flaskify(result) @app.route('/product//tracks', methods=['PUT']) @validation.raml_validate def product_physical_create_tracks(product_id): """Add/Update/Delete tracks for a product. Returns: flask.Response: JSON array representing the new list of tracks. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) data = request.get_json() result = product_physical_tracks.update_tracks( product_id, data['items'], account_type, account_id) return flaskify(result) @app.route( '/product//tracks/publishing-obligation', methods=['GET']) @validation.raml_validate def get_tracks_publishing_obligations_data_for_product(product_id): """GET publishing obligation data for tracks in product. Args: product_id (int): the product_id (release_id) of the product. 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) result = product_physical_tracks.get_publishing_obligations_for_product( product_id=product_id, account_type=account_type, account_id=account_id) return flaskify(result) @app.route( '/product//tracks/publishing-obligation', methods=['PUT']) @validation.raml_validate def put_tracks_publishing_obligations_data_for_product(product_id): """GET publishing obligation data for tracks in product. Args: product_id (int): the product_id (release_id) of the product. 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) data = request.get_json() result = product_physical_tracks.update_publishing_obligations( product_id=product_id, data=data, account_type=account_type, account_id=account_id) return flaskify(result) @app.route('/product//inventory', methods=['GET']) @validation.raml_validate def product_physical_inventory_fetch(product_id): """Fetch physical product inventory by it's primary key, product_id. Returns: flask.Response: JSON object representing the physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_physical.get_inventory( product_id, account_type, account_id) return flaskify(result) @app.route('/product//change_history', methods=['POST']) def product_physical_change_history_post(product_id): """Create a new entry in the physical product change history table. Returns: flask.Response: JSON object representing the physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) data = request.get_json() result = product_physical_change_history.create( product_id, account_type, account_id, **data) return flaskify(result) @app.route('/products//change-history', methods=['GET']) @handler_utils.parse_and_validate_request_json(GetProductPhysicalChangeHistorySchema) # noqa def get_physical_product_change_history(store_id, json_data): """Get Product's change history data from Snowflake. Returns: flask.Response: JSON object representing the physical product. """ request_context_valid = request_context.validate_request_context( g.request_context, role=header.DELIVER_PHYSICAL_AUDIO_ROLE) if not request_context_valid: return flaskify(request_context_valid) result = product_delivery.get_physical_product_change_history( **json_data) return flaskify(result) @app.route('/product//supply_chain_defaults', methods=['POST']) def set_supply_chain_defaults_for_product(product_id): """Create a new entry in the physical product supply chain info table. Returns: flask.Response: Returns supply chain info for physical product. """ result = product_physical.product_physical_insert_defaults(product_id) return flaskify(result) @app.route('/product//supply-chain-metadata', methods=['GET']) def get_product_physical_supply_chain_metadata(product_id): """Return a list of all supply chain metadata. Returns: flask.Response: result of supply chain metadata. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_physical_supply_chain_metadata. \ get_product_supply_chain_metadata( product_id, account_type, account_id) return flaskify(result) @app.route('/product//supply-chain-defaults', methods=['GET']) def get_supply_chain_defaults_by_product_id(product_id): """Fetch record by product id from product supply chain info table. Returns: flask.Response: Returns supply chain info for physical product_id. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_physical.get_supply_chain_defaults_by_product_id( product_id, account_type, account_id) return flaskify(result) @app.route('/product//supply-chain-metadata', methods=['POST']) def set_physical_supply_chain_metadata(product_id): """Set metadata in the product_physical_supply_chain_metadata table. Returns: flask.Response: Response of supply chain metadata of physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) data = request.get_json() result = product_physical_supply_chain_metadata. \ set_physical_supply_chain_metadata( product_id, data, account_type, account_id) return flaskify(result) @app.route( '/product//supply-chain-metadata//delete', methods=['DELETE']) def delete_supply_chain_metadata(product_id, metadata_id): """Soft delete an existing record by setting is_deleted flag as 1. Returns: flask.Response: Includes a response code of the operation """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = \ product_physical_supply_chain_metadata.delete_supply_chain_metadata( product_id, metadata_id, account_type, account_id) return flaskify(result) @app.route( '/product//supply-chain-defaults', methods=['PUT']) def update_product_physical_supply_chain_info(product_id): """Update a list of all supply chain info. Returns: flask.Response: updated result of supply chain info. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) supplychain_info = request.get_json() result = product_physical.update_product_supply_chain_info( supplychain_info, product_id, account_type, account_id) return flaskify(result) @app.route( '/product//supply-chain-metadata/', methods=['DELETE']) def delete_supply_chain_metadata_by_product_and_store(product_id, store_id): """Soft delete an existing record by setting is_deleted flag as 1. Returns: flask.Response: Includes a response code of the operation """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) orchard_user_id = request.headers.get(header.ORCHARD_USER_ID) result = \ product_physical_supply_chain_metadata \ .delete_supply_chain_metadata_by_product_and_store( product_id, store_id, orchard_user_id, account_type, account_id) return flaskify(result) @app.route('/product//supply-chain-info', methods=['POST']) def set_supply_chain_info_by_product_id(product_id): """Create entry in product_physical_supply_chain_info table. Returns: flask.Response: Returns created supply chain info for physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) data = request.get_json() result = product_physical. \ set_supply_chain_info_by_product_id( product_id, data, account_type, account_id) return flaskify(result) @app.route('/product-distribution/', methods=['GET']) def product_distribution_fetch(product_id): """Retrieves a list of product distribution objects for given product_id. Args: product_id (int): the product_id (release_id) of the product. Returns: flask.Response: Returns distribution details of physical product. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_distribution. \ fetch_product_distribution(product_id, account_type, account_id) return flaskify(result) @app.route('/product-distribution/', methods=['DELETE']) def product_distribution_delete(product_id): """Delete all the existing product distribution for given product_id. Returns: flask.Response: Includes a response code of the operation """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) result = product_distribution. \ delete_product_distribution(product_id, account_type, account_id) return flaskify(result) @app.route('/product-distribution/', methods=['POST']) def save_product_distribution(product_id): """Save product distribution data for given product_id. Returns: flask.Response: Returns 200 on successful save. """ request_data = request.get_json() result = product_distribution. \ save_product_distribution( product_id=product_id, request_data=request_data, user=request.headers.get(header.ORCHARD_USER_ID), account_type=request.headers.get(header.GRASS_ACCOUNT_TYPE), account_id=request.headers.get(header.GRASS_ACCOUNT_ID)) return flaskify(result) @app.route('/orders', methods=['GET']) @handler_utils.parse_and_validate_request_json(get_orders.GetOrdersSchema) def get_orders(json_data): """Retrieves a list of orders for given order ids. Args: json_data (dict): dict of params parsed by GetOrdersSchema Returns: flask.Response: result of fetching orders. """ request_context_valid = request_context.validate_request_context( g.request_context, role=header.DELIVER_PHYSICAL_AUDIO_ROLE) if not request_context_valid: return flaskify(request_context_valid) result = orders.get_orders(**json_data) return flaskify(result) @app.route('/order', methods=['POST']) @handler_utils.parse_and_validate_request_json( create_order.PostOrderPayloadSchema) def create_order(json_data): """Creates an order. Returns: flask.Response: result of creating the order. """ request_context_valid = request_context.validate_request_context( g.request_context, role=header.DELIVER_PHYSICAL_AUDIO_ROLE) if not request_context_valid: return flaskify(request_context_valid) result = orders.create_order( identity_id=g.request_context.identity_id, delivery_store_id=json_data['delivery_store_id'], product_ids=json_data['product_ids'], delivery_type=json_data['delivery_type'], ) return flaskify(result) @app.route('/orders', methods=['PATCH']) @handler_utils.parse_and_validate_request_json( update_order.PatchOrdersPayloadSchema ) def update_orders(json_data): """Updates orders. Returns: flask.Response: result of updating the orders. """ request_context_valid = request_context.validate_request_context( g.request_context, role=header.DELIVER_PHYSICAL_AUDIO_ROLE ) if not request_context_valid: return flaskify(request_context_valid) result = orders.update_orders( identity_id=g.request_context.identity_id, orders=json_data['items'] ) return flaskify(result) @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_DSN 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))