"""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. """ from flask import g, jsonify, request from oto import response from oto import status as http_status from oto.adaptors.flask import flaskify from owsrequest.constants import headers as owsrequest_headers from timed_release import config from timed_release.api import app from timed_release.constants import error from timed_release.logic import ( hello, product_live_time, scheduled_update, timed_release, timed_release_ws, ) from timed_release.utils.handler_utils import is_jwt_identity_authorized from timed_release.utils.validations import ( validate_profile_access, validate_request_headers, ) from timed_release.validation import helper, json_schema from timed_release.validation.schema import ( post_request_schema, post_scheduled_update, put_product_schedule_ws, put_request_schema_timed_release, ) @app.route('/', methods=['GET']) def hello_world(): """Hello World with an optional GET param "name".""" name = request.args.get('name', '') return flaskify(hello.say_hello(name)) @app.route('/', methods=['GET']) def hello_world_username(username): """Hello World on /. Args: username (str): the user's username. """ return flaskify(hello.say_hello(username)) @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 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('/product/', methods=['GET']) def get_product_live_detail_by_id(product_id): """Get product live time details for given product id. Args: product_id (str): Product id to fetch Spotify product live time. Returns: flask.response: Response contains dict describing product live time details, or validation message. """ return flaskify(product_live_time.get_product_live_time_details( product_id)) @app.route('/product', methods=['POST']) @helper.validate_json @json_schema.validate_body(request, post_request_schema.schema) def create_product_live_time_detail(): """Save info about product live time detail. Returns: flask.Response: Response contains dict describing product live time details, or validation message. """ return flaskify(product_live_time.create_product_live_time_detail( request.get_json())) @app.route('/product/', methods=['DELETE']) def delete_product_live_details(product_id): """Delete product live time details by product id. Args: product_id (str): Product id to delete product live time details. Returns: flask.Response: Response containing delete success message or error response message. """ return flaskify(product_live_time.delete_product_live_time_details( product_id)) @app.route('/product/', methods=['PUT']) @helper.validate_json @json_schema.validate_body(request, post_request_schema.schema) def update_product_live_detail_by_id(product_id): """Update product live time details for given product id. Args: product_id (str): Product id to update product live time details. Returns: flask.response: Response contains update success message. error response message otherwise. """ return flaskify(product_live_time.update_product_live_time( product_id, request.get_json())) @app.route('/timedrelease/', methods=['GET']) def get_timed_release_data(product_id): """ Fetch all timed release data for the given product id. Returns: flask.Response: on successful, 200 status with JSON body. """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) jwt_identity_id = g.request_context.jwt_identity_id if not orchard_identity_id and not jwt_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) validate_access = validate_profile_access(request) if not validate_access: if not is_jwt_identity_authorized(jwt_identity_id): return flaskify(response.create_error_response( error.ERROR_DONT_HAVE_PERMISSIONS, error.ERROR_MESSAGE_FORBIDDEN_USER, status=403)) return flaskify(timed_release.get_timed_release_data(product_id)) @app.route('/timedrelease/', methods=['PUT']) @helper.validate_json @json_schema.validate_body(request, put_request_schema_timed_release.schema) def update_timed_release_data(product_id): """Update product live time details for given product id. Args: product_id (str): Product id to update product live time details. Returns: flask.response: Response contains update success message. error response message otherwise. """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) if not orchard_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) validate_access = validate_profile_access(request) if not validate_access: return flaskify(response.create_error_response( error.ERROR_DONT_HAVE_PERMISSIONS, error.ERROR_MESSAGE_FORBIDDEN_USER, status=403)) return flaskify(timed_release.update_timed_release_data( product_id, request.get_json(), orchard_identity_id)) @app.route('/scheduled_update/', methods=['POST']) @helper.validate_json @json_schema.validate_body(request, post_scheduled_update.schema) def upsert_scheduled_update(product_id): """Update/insert scheduled update for a given product id. Args: product_id (str): Product id to update/insert scheduled update for. Returns: flask.response: Response containing update/insert success. error response message otherwise. """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) if not orchard_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) return flaskify(scheduled_update.upsert( product_id, request.get_json(), orchard_identity_id)) @app.route('/scheduled_update/', methods=['GET']) def get_scheduled_update(product_id): """Get scheduled update for a given product id. Args: product_id (str): Product id to get scheduled update for. Returns: flask.response: Response containing scheduled updates for product. error response message otherwise. """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) if not orchard_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) return flaskify(scheduled_update.get_scheduled_update(product_id)) @app.route('/scheduled_update/', methods=['DELETE']) def delete_scheduled_update(product_id): """Delete scheduled update for a given product id. Args: product_id (str): Product id to demete scheduled update for. Returns: flask.response: Response containing delete success. error response message otherwise. """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) if not orchard_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) return flaskify(scheduled_update.delete(product_id)) @app.route('/scheduled_update//execute', methods=['POST']) def execute_scheduled_update(product_id): """Execute scheduled update for a given product id. Args: product_id (str): product id to execute scheduled update for Returns: flask.response: Response containing success message """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) if not orchard_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) name = request.args['name'] if 'name' in request.args else None return flaskify(scheduled_update.execute_scheduled_update( product_id, name)) @app.route('/product//timed_release/ws', methods=['PUT']) @helper.validate_json @json_schema.validate_body(request, put_product_schedule_ws.schema) def upsert_product_timed_release_and_schedule(product_id): """Upsert workstation timed release and scheduled updated for product id. This would be consumed in workstation only. It supports both headers based identity/profile validation used by the graphql layer and JWT-based validation used by ows-delivery-metadata. Args: product_id (str): Identifier of the product whose timed release and and scheduled update needs to be upserted. Returns: flask.response: Response contains update success message. error response message otherwise. """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) jwt_identity_id = g.request_context.jwt_identity_id identity_id = orchard_identity_id or jwt_identity_id if not orchard_identity_id and not jwt_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) validate_access = validate_profile_access(request) if not validate_access and not is_jwt_identity_authorized(jwt_identity_id): return flaskify(response.create_error_response( error.ERROR_DONT_HAVE_PERMISSIONS, error.ERROR_MESSAGE_FORBIDDEN_USER, status=http_status.FORBIDDEN)) return flaskify(timed_release_ws.upsert_timed_release_and_schedule( product_id, request.get_json(), identity_id, request.headers)) @app.route( '/product//timed_release/store/', methods=['GET'] ) def get_product_timed_release_by_store_id(product_id, store_id): """Get timed release details for a given product and store. It supports both headers based identity/profile validation used by the graphql layer and JWT-based validation used by ows-delivery-metadata. This will be consumed by VECTOR only. Args: product_id (str): Product id to get scheduled update for. Returns: flask.response: Response containing scheduled updates for product. error response message otherwise. """ orchard_identity_id = request.headers.get( owsrequest_headers.ORCHARD_IDENTITY_ID) jwt_identity_id = g.request_context.jwt_identity_id if not orchard_identity_id and not jwt_identity_id: return flaskify(response.create_error_response( error.ERROR_CODE_BAD_REQUEST, error.ERROR_CODE_AUTHORIZATION)) validate_access = validate_profile_access(request) if not validate_access and not is_jwt_identity_authorized(jwt_identity_id): return flaskify(response.create_error_response( error.ERROR_DONT_HAVE_PERMISSIONS, error.ERROR_MESSAGE_FORBIDDEN_USER, status=http_status.FORBIDDEN)) return flaskify(timed_release.get_product_timed_release_by_store_id( product_id, store_id)) @app.route( '/product//timed_release/ws', methods=['GET'] ) def get_product_timed_release_for_ws(product_id): """Get timed release for product_id set via workstation. It supports both headers based identity/profile validation used by the graphql layer and JWT-based validation used by ows-delivery-metadata. Args: product_id (str): Product id to get scheduled update for. Returns: flask.response: Response containing timed release for product. error response message otherwise. """ validation_response = validate_request_headers(request) if validation_response.status != http_status.OK: return validation_response return flaskify(timed_release_ws.get_product_timed_release_for_ws( product_id)) @app.route('/product//timed_release/ws', methods=['DELETE']) def delete_timed_release_ws(product_id): """Delete workstation set timed release for a given product id. Args: product_id (str): Product id to delete schedule for. Returns: flask.response: Response containing delete success. error response message otherwise. """ validation_response = validate_request_headers(request) if validation_response.status != http_status.OK: return validation_response return flaskify( timed_release_ws.delete_timed_releases_and_scheduled_updates_ws( product_id ) )