"""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 import uuid from flask import g from flask import jsonify from flask import request from oto import response as oto_response from oto.adaptors.flask import flaskify from availability import config from availability import utils from availability.api import app from availability.connectors import loggly from availability.constants import header as header_const from availability.logic import product_store from availability.logic import product_submission from availability.logic import release_fetching from availability.logic import store from availability.logic import task from availability.logic.queue import producer from availability.validation import json_schema from availability.validation import ownership from availability.validation import query_params from availability.validation.schema import endpoint_req_body from availability.validation.schema import header from availability.validation.schema import product_status logger = loggly.get_current_logger() @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.route(config.TIMEOUT_CHECK, methods=['GET']) def sleep(seconds): """Check the actual request timeout of the deployed application.""" time.sleep(int(seconds)) return jsonify({'status': 'ok'}) @app.errorhandler(500) def exception_handler(error): """Exception 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 flaskify(oto_response.create_fatal_response(message)) @app.route('/admin/products', methods=['POST']) @json_schema.validate_headers(request, header.schema) @json_schema.validate_body( request, endpoint_req_body.add_products_to_poll_schema) def add_products_to_poll(): """Add new products for polling details. Returns: flask.Response: Response with JSON 'code' and 'message' as result of submitting products to poll. """ request_data = request.get_json() response = product_submission.submit_to_poll(request_data['products']) return flaskify(response) @app.route('/admin/produce-job-messages/', methods=['POST']) @json_schema.validate_headers(request, header.schema) def produce_job_messages(store_id): """Produce polling job messages for specified store. Args: store_id (int): ID of the store for which polling job messages should be produced. Returns: flask.Response: Response with success message in payload if polling job messages were produced or with error in case of failure. """ validation_response = query_params.validate_store_id(store_id) if not validation_response: return flaskify(validation_response) store_response = store.get_store_by_id(validation_response.message) if not store_response: return flaskify(store_response) current_date_override_arg = request.form.get('current_date_override') logging_context = dict(current_date_override_arg=current_date_override_arg) if current_date_override_arg: logger.info( 'Received current_date_override in the POST body', resources=logging_context) current_date_override = utils.to_datetime(current_date_override_arg) logging_context['current_date_override'] = current_date_override logger.info('Overriding current date', resources=logging_context) else: current_date_override = None tasks_reset_response = task.reset_stuck_tasks( store_id, current_date_override=current_date_override) if not tasks_reset_response: return flaskify(tasks_reset_response) new_correlation_id = str(uuid.uuid4()) response = producer.put_products_to_poll_messages_to_queue( store_id=store_id, correlation_id=request.headers.get( 'Correlation-Id', new_correlation_id), current_date_override=current_date_override, ) if response: message = ( 'Producing polling job messages for store {store_id} ' 'was triggered.'.format(store_id=store_id)) response.message = {'message': message} return flaskify(response) @app.route('/status', methods=['GET']) @json_schema.validate_headers(request, product_status.schema) def get_products_status(): """Get release status of products for each store where it exists. Args: product_ids (str): query parameter that should be comma-separated IDs of products which status should be checked. Returns: flask.Response: List of product statuses for each store. """ product_ids_response = query_params.validate_product_ids_parameter( request.args) if not product_ids_response: return flaskify(product_ids_response) account_type = request.headers.get(header_const.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header_const.GRASS_ACCOUNT_ID) if account_type and account_id: data_to_validate = { 'product_ids': product_ids_response.message, 'account_type': account_type, 'account_id': account_id, 'correlation_id': request.headers.get( header_const.CORRELATION_ID, str(uuid.uuid4())) } ownership_response = ownership.check_products_ownership( **data_to_validate) if not ownership_response: return flaskify(ownership_response) result = release_fetching.get_status_for_products( product_ids_response.message) return flaskify(result) @app.route('/admin/import_store_links', methods=['POST']) @json_schema.validate_headers(request, header.schema) def import_store_links(): """Import store links.""" result = product_store.import_store_links(request.get_json()) return flaskify(result)